feat: started some more work on getting this into shape

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2023-09-28 13:14:35 +02:00
committed by Patrik Oldsberg
parent 95415da622
commit 48db8c25eb
85 changed files with 311 additions and 1065 deletions
@@ -0,0 +1,15 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@@ -0,0 +1,16 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './types';
@@ -1,5 +1,5 @@
/*
* Copyright 2022 The Backstage Authors
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,42 @@
* limitations under the License.
*/
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import type { FormProps as SchemaFormProps } from '@rjsf/core-v5';
import { UiSchema } from '@rjsf/utils';
import { JsonObject } from '@backstage/types';
// TODO(Rugvip): The FormProps type is actually supposed to be alpha, but since we want to
// refer to it from @backstage/plugin-scaffolder, it needs to be public for now.
// Once we support internal alpha re-exports this should be switched to an alpha export.
/** @public */
export type TemplateGroupFilter = {
title?: React.ReactNode;
filter: (entity: TemplateEntityV1beta3) => boolean;
};
/**
* Any `@rjsf/core` form properties that are publicly exposed to the `NextScaffolderpage`
* Any `@rjsf/core` form properties that are publicly exposed to the `ScaffolderPage`
*
* @alpha
* @public
*/
export type FormProps = Pick<
SchemaFormProps,
'transformErrors' | 'noHtml5Validate'
>;
/**
* The props for the Last Step in scaffolder template form.
* Which represents the summary of the input provided by the end user.
*
* @public
*/
export type ReviewStepProps = {
disableButtons: boolean;
formData: JsonObject;
handleBack: () => void;
handleReset: () => void;
handleCreate: () => void;
steps: {
uiSchema: UiSchema;
mergedSchema: JsonObject;
schema: JsonObject;
}[];
};
@@ -19,6 +19,7 @@ import {
FieldExtensionOptions,
FieldExtensionComponentProps,
FieldExtensionUiSchema,
CustomFieldExtensionSchema,
} from './types';
import { Extension, attachComponentData } from '@backstage/core-plugin-api';
import { UIOptionsType } from '@rjsf/utils';
@@ -77,4 +78,7 @@ export type {
FieldExtensionOptions,
FieldExtensionComponentProps,
FieldExtensionUiSchema,
CustomFieldExtensionSchema,
};
export * from './rjsf';
+1
View File
@@ -15,6 +15,7 @@
*/
export * from './extensions';
export * from './components';
export * from './types';
export * from './secrets';
export * from './api';
@@ -15,9 +15,9 @@
*/
import {
CustomFieldValidator,
FieldExtensionOptions,
FieldExtensionComponentProps,
LegacyCustomFieldValidator,
LegacyFieldExtensionOptions,
LegacyFieldExtensionComponentProps,
} from './types';
import { Extension, attachComponentData } from '@backstage/core-plugin-api';
import { FIELD_EXTENSION_KEY } from '../../extensions/keys';
@@ -32,7 +32,7 @@ export function createLegacyScaffolderFieldExtension<
TReturnValue = unknown,
TInputProps = unknown,
>(
options: FieldExtensionOptions<TReturnValue, TInputProps>,
options: LegacyFieldExtensionOptions<TReturnValue, TInputProps>,
): Extension<FieldExtensionComponent<TReturnValue, TInputProps>> {
return {
expose() {
@@ -50,7 +50,7 @@ export function createLegacyScaffolderFieldExtension<
}
export type {
CustomFieldValidator,
FieldExtensionOptions,
FieldExtensionComponentProps,
LegacyCustomFieldValidator,
LegacyFieldExtensionOptions,
LegacyFieldExtensionComponentProps,
};
@@ -22,7 +22,7 @@ import { CustomFieldExtensionSchema } from '../../extensions/types';
*
* @alpha
*/
export type CustomFieldValidator<TFieldReturnValue> = (
export type LegacyCustomFieldValidator<TFieldReturnValue> = (
data: TFieldReturnValue,
field: FieldValidation,
context: { apiHolder: ApiHolder },
@@ -34,15 +34,15 @@ export type CustomFieldValidator<TFieldReturnValue> = (
*
* @alpha
*/
export type FieldExtensionOptions<
export type LegacyFieldExtensionOptions<
TFieldReturnValue = unknown,
TInputProps = unknown,
> = {
name: string;
component: (
props: FieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
props: LegacyFieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
) => JSX.Element | null;
validation?: CustomFieldValidator<TFieldReturnValue>;
validation?: LegacyCustomFieldValidator<TFieldReturnValue>;
schema?: CustomFieldExtensionSchema;
};
@@ -52,7 +52,7 @@ export type FieldExtensionOptions<
*
* @alpha
*/
export interface FieldExtensionComponentProps<
export interface LegacyFieldExtensionComponentProps<
TFieldReturnValue,
TUiOptions = unknown,
> extends FieldProps<TFieldReturnValue> {
@@ -25,8 +25,13 @@ import {
} from '@material-ui/core';
import { type IChangeEvent } from '@rjsf/core-v5';
import { ErrorSchema } from '@rjsf/utils';
import React, { useCallback, useMemo, useState, type ReactNode } from 'react';
import { NextFieldExtensionOptions } from '../../../extensions';
import React, {
useCallback,
useMemo,
useState,
type ReactNode,
ComponentType,
} from 'react';
import {
createAsyncValidators,
type FormValidation,
@@ -35,7 +40,6 @@ import { ReviewState, type ReviewStateProps } from '../ReviewState';
import { useTemplateSchema } from '../../hooks/useTemplateSchema';
import validator from '@rjsf/validator-ajv8';
import { useFormDataFromQuery } from '../../hooks';
import { FormProps } from '../../types';
import { useTransformSchemaToProps } from '../../hooks/useTransformSchemaToProps';
import { hasErrors } from './utils';
import * as FieldOverrides from './FieldOverrides';
@@ -43,7 +47,10 @@ import { Form } from '../Form';
import {
TemplateParameterSchema,
LayoutOptions,
FieldExtensionOptions,
FormProps,
} from '@backstage/plugin-scaffolder-react';
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
const useStyles = makeStyles(theme => ({
backButton: {
@@ -66,12 +73,13 @@ const useStyles = makeStyles(theme => ({
*/
export type StepperProps = {
manifest: TemplateParameterSchema;
extensions: NextFieldExtensionOptions<any, any>[];
extensions: FieldExtensionOptions<any, any>[];
templateName?: string;
FormProps?: FormProps;
formProps?: FormProps;
initialState?: Record<string, JsonValue>;
onCreate: (values: Record<string, JsonValue>) => Promise<void>;
components?: {
ReviewStepComponent?: ComponentType<ReviewStepProps>;
ReviewStateComponent?: (props: ReviewStateProps) => JSX.Element;
createButtonText?: ReactNode;
reviewButtonText?: ReactNode;
@@ -87,6 +95,7 @@ export const Stepper = (stepperProps: StepperProps) => {
const { layouts = [], components = {}, ...props } = stepperProps;
const {
ReviewStateComponent = ReviewState,
ReviewStepComponent,
createButtonText = 'Create',
reviewButtonText = 'Review',
} = components;
@@ -128,6 +137,13 @@ export const Stepper = (stepperProps: StepperProps) => {
[setFormState],
);
const handleCreate = useCallback(() => {
props.onCreate(formState);
const name =
typeof formState.name === 'string' ? formState.name : undefined;
analytics.captureEvent('create', name ?? props.templateName ?? 'unknown');
}, [props, formState, analytics]);
const currentStep = useTransformSchemaToProps(steps[activeStep], { layouts });
const handleNext = async ({
@@ -171,6 +187,7 @@ export const Stepper = (stepperProps: StepperProps) => {
</MuiStep>
</MuiStepper>
<div className={styles.formWrapper}>
{/* eslint-disable-next-line no-nested-ternary */}
{activeStep < steps.length ? (
<Form
validator={validator}
@@ -183,7 +200,7 @@ export const Stepper = (stepperProps: StepperProps) => {
fields={{ ...FieldOverrides, ...extensions }}
showErrorList={false}
onChange={handleChange}
{...(props.FormProps ?? {})}
{...(props.formProps ?? {})}
>
<div className={styles.footer}>
<Button
@@ -203,6 +220,16 @@ export const Stepper = (stepperProps: StepperProps) => {
</Button>
</div>
</Form>
) : // TODO: potentially move away from this pattern, deprecate?
ReviewStepComponent ? (
<ReviewStepComponent
disableButtons={isValidating}
formData={formState}
handleBack={handleBack}
handleReset={() => {}}
steps={steps}
handleCreate={handleCreate}
/>
) : (
<>
<ReviewStateComponent formState={formState} schemas={steps} />
@@ -217,17 +244,7 @@ export const Stepper = (stepperProps: StepperProps) => {
<Button
variant="contained"
color="primary"
onClick={() => {
props.onCreate(formState);
const name =
typeof formState.name === 'string'
? formState.name
: undefined;
analytics.captureEvent(
'create',
name ?? props.templateName ?? 'unknown',
);
}}
onClick={handleCreate}
>
{createButtonText}
</Button>
@@ -23,15 +23,8 @@ import {
import { Progress, Link } from '@backstage/core-components';
import { Typography } from '@material-ui/core';
import { errorApiRef, IconComponent, useApi } from '@backstage/core-plugin-api';
import { TemplateGroup } from '@backstage/plugin-scaffolder-react/alpha';
/**
* @alpha
*/
export type TemplateGroupFilter = {
title?: React.ReactNode;
filter: (entity: TemplateEntityV1beta3) => boolean;
};
import { TemplateGroupFilter } from '../../../components';
import { TemplateGroup } from '../TemplateGroup/TemplateGroup';
/**
* @alpha
@@ -27,6 +27,7 @@ import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema';
import { Stepper, type StepperProps } from '../Stepper/Stepper';
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
import { ReviewStepProps } from '../../../components';
const useStyles = makeStyles<BackstageTheme>(() => ({
markdown: {
@@ -48,11 +49,14 @@ export type WorkflowProps = {
description?: string;
namespace: string;
templateName: string;
components?: {
ReviewStepComponent?: React.ComponentType<ReviewStepProps>;
};
onError(error: Error | undefined): JSX.Element | null;
} & Pick<
StepperProps,
| 'extensions'
| 'FormProps'
| 'formProps'
| 'components'
| 'onCreate'
| 'initialState'