diff --git a/plugins/scaffolder-react/src/components/ReviewStep.tsx b/plugins/scaffolder-react/src/components/ReviewStep.tsx new file mode 100644 index 0000000000..eb2f165350 --- /dev/null +++ b/plugins/scaffolder-react/src/components/ReviewStep.tsx @@ -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. + */ diff --git a/plugins/scaffolder-react/src/components/index.ts b/plugins/scaffolder-react/src/components/index.ts new file mode 100644 index 0000000000..92cb965d75 --- /dev/null +++ b/plugins/scaffolder-react/src/components/index.ts @@ -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'; diff --git a/plugins/scaffolder-react/src/next/types.ts b/plugins/scaffolder-react/src/components/types.ts similarity index 50% rename from plugins/scaffolder-react/src/next/types.ts rename to plugins/scaffolder-react/src/components/types.ts index 192fdf4146..f6e186a56f 100644 --- a/plugins/scaffolder-react/src/next/types.ts +++ b/plugins/scaffolder-react/src/components/types.ts @@ -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; + }[]; +}; diff --git a/plugins/scaffolder-react/src/extensions/index.tsx b/plugins/scaffolder-react/src/extensions/index.tsx index 395a15f902..5372a8cf7e 100644 --- a/plugins/scaffolder-react/src/extensions/index.tsx +++ b/plugins/scaffolder-react/src/extensions/index.tsx @@ -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'; diff --git a/plugins/scaffolder-react/src/index.ts b/plugins/scaffolder-react/src/index.ts index 1b20c19414..70560cc694 100644 --- a/plugins/scaffolder-react/src/index.ts +++ b/plugins/scaffolder-react/src/index.ts @@ -15,6 +15,7 @@ */ export * from './extensions'; +export * from './components'; export * from './types'; export * from './secrets'; export * from './api'; diff --git a/plugins/scaffolder-react/src/legacy/extensions/index.tsx b/plugins/scaffolder-react/src/legacy/extensions/index.tsx index 449aa74c22..5276d9d31c 100644 --- a/plugins/scaffolder-react/src/legacy/extensions/index.tsx +++ b/plugins/scaffolder-react/src/legacy/extensions/index.tsx @@ -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, + options: LegacyFieldExtensionOptions, ): Extension> { return { expose() { @@ -50,7 +50,7 @@ export function createLegacyScaffolderFieldExtension< } export type { - CustomFieldValidator, - FieldExtensionOptions, - FieldExtensionComponentProps, + LegacyCustomFieldValidator, + LegacyFieldExtensionOptions, + LegacyFieldExtensionComponentProps, }; diff --git a/plugins/scaffolder-react/src/legacy/extensions/types.ts b/plugins/scaffolder-react/src/legacy/extensions/types.ts index 1c94626b03..80b2903b09 100644 --- a/plugins/scaffolder-react/src/legacy/extensions/types.ts +++ b/plugins/scaffolder-react/src/legacy/extensions/types.ts @@ -22,7 +22,7 @@ import { CustomFieldExtensionSchema } from '../../extensions/types'; * * @alpha */ -export type CustomFieldValidator = ( +export type LegacyCustomFieldValidator = ( data: TFieldReturnValue, field: FieldValidation, context: { apiHolder: ApiHolder }, @@ -34,15 +34,15 @@ export type CustomFieldValidator = ( * * @alpha */ -export type FieldExtensionOptions< +export type LegacyFieldExtensionOptions< TFieldReturnValue = unknown, TInputProps = unknown, > = { name: string; component: ( - props: FieldExtensionComponentProps, + props: LegacyFieldExtensionComponentProps, ) => JSX.Element | null; - validation?: CustomFieldValidator; + validation?: LegacyCustomFieldValidator; schema?: CustomFieldExtensionSchema; }; @@ -52,7 +52,7 @@ export type FieldExtensionOptions< * * @alpha */ -export interface FieldExtensionComponentProps< +export interface LegacyFieldExtensionComponentProps< TFieldReturnValue, TUiOptions = unknown, > extends FieldProps { diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 3d2e1233c0..9bb8084ac3 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -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[]; + extensions: FieldExtensionOptions[]; templateName?: string; - FormProps?: FormProps; + formProps?: FormProps; initialState?: Record; onCreate: (values: Record) => Promise; components?: { + ReviewStepComponent?: ComponentType; 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) => {
+ {/* eslint-disable-next-line no-nested-ternary */} {activeStep < steps.length ? (
{ fields={{ ...FieldOverrides, ...extensions }} showErrorList={false} onChange={handleChange} - {...(props.FormProps ?? {})} + {...(props.formProps ?? {})} >
+ ) : // TODO: potentially move away from this pattern, deprecate? + ReviewStepComponent ? ( + {}} + steps={steps} + handleCreate={handleCreate} + /> ) : ( <> @@ -217,17 +244,7 @@ export const Stepper = (stepperProps: StepperProps) => { diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx index f0f06b3410..ec08505c06 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx @@ -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 diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 228f5cefd9..c0d6b40333 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -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(() => ({ markdown: { @@ -48,11 +49,14 @@ export type WorkflowProps = { description?: string; namespace: string; templateName: string; + components?: { + ReviewStepComponent?: React.ComponentType; + }; onError(error: Error | undefined): JSX.Element | null; } & Pick< StepperProps, | 'extensions' - | 'FormProps' + | 'formProps' | 'components' | 'onCreate' | 'initialState' diff --git a/plugins/scaffolder/src/alpha.ts b/plugins/scaffolder/src/alpha.ts index d2bd762190..92ec945de4 100644 --- a/plugins/scaffolder/src/alpha.ts +++ b/plugins/scaffolder/src/alpha.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -export { NextScaffolderPage } from './plugin'; export { - type NextRouterProps, type FormProps, type TemplateListPageProps, type TemplateWizardPageProps, } from './next'; + +export * from './legacy'; diff --git a/plugins/scaffolder/src/next/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx similarity index 100% rename from plugins/scaffolder/src/next/OngoingTask/ContextMenu.tsx rename to plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx diff --git a/plugins/scaffolder/src/next/OngoingTask/OngoingTask.test.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx similarity index 100% rename from plugins/scaffolder/src/next/OngoingTask/OngoingTask.test.tsx rename to plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx diff --git a/plugins/scaffolder/src/next/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx similarity index 100% rename from plugins/scaffolder/src/next/OngoingTask/OngoingTask.tsx rename to plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx diff --git a/plugins/scaffolder/src/next/OngoingTask/index.ts b/plugins/scaffolder/src/components/OngoingTask/index.ts similarity index 100% rename from plugins/scaffolder/src/next/OngoingTask/index.ts rename to plugins/scaffolder/src/components/OngoingTask/index.ts diff --git a/plugins/scaffolder/src/next/Router/Router.test.tsx b/plugins/scaffolder/src/components/Router/Router.test.tsx similarity index 94% rename from plugins/scaffolder/src/next/Router/Router.test.tsx rename to plugins/scaffolder/src/components/Router/Router.test.tsx index 3d52dd0dd0..2dc67a76be 100644 --- a/plugins/scaffolder/src/next/Router/Router.test.tsx +++ b/plugins/scaffolder/src/components/Router/Router.test.tsx @@ -14,8 +14,6 @@ * limitations under the License. */ import React from 'react'; -import { TemplateListPage } from '../TemplateListPage'; -import { TemplateWizardPage } from '../TemplateWizardPage'; import { Router } from './Router'; import { renderInTestApp } from '@backstage/test-utils'; import { @@ -27,6 +25,7 @@ import { createScaffolderLayout, ScaffolderLayouts, } from '@backstage/plugin-scaffolder-react'; +import { TemplateListPage, TemplateWizardPage } from '../../next'; jest.mock('../TemplateListPage', () => ({ TemplateListPage: jest.fn(() => null), @@ -52,7 +51,7 @@ describe('Router', () => { const { getByText } = await renderInTestApp( <>foobar, + EXPERIMENTAL_TemplateListPageComponent: () => <>foobar, }} />, { @@ -77,7 +76,7 @@ describe('Router', () => { const { getByText } = await renderInTestApp( <>foobar, + EXPERIMENTAL_TemplateWizardPageComponent: () => <>foobar, }} />, { @@ -93,7 +92,7 @@ describe('Router', () => { await renderInTestApp( ; TemplateCardComponent?: React.ComponentType<{ template: TemplateEntityV1beta3; }>; TaskPageComponent?: React.ComponentType>; - TemplateOutputsComponent?: React.ComponentType<{ + EXPERIMENTAL_TemplateOutputsComponent?: React.ComponentType<{ output?: ScaffolderTaskOutput; }>; - TemplateListPageComponent?: React.ComponentType; - TemplateWizardPageComponent?: React.ComponentType; + EXPERIMENTAL_TemplateListPageComponent?: React.ComponentType; + EXPERIMENTAL_TemplateWizardPageComponent?: React.ComponentType; }; groups?: TemplateGroupFilter[]; templateFilter?: (entity: TemplateEntityV1beta3) => boolean; - // todo(blam): rename this to formProps - FormProps?: FormProps; + formProps?: FormProps; contextMenu?: { /** Whether to show a link to the template editor */ editor?: boolean; @@ -82,21 +87,24 @@ export type NextRouterProps = { /** * The Scaffolder Router * - * @alpha + * @public */ -export const Router = (props: PropsWithChildren) => { +export const Router = (props: PropsWithChildren) => { const { components: { TemplateCardComponent, - TemplateOutputsComponent, TaskPageComponent = OngoingTask, - TemplateListPageComponent = TemplateListPage, - TemplateWizardPageComponent = TemplateWizardPage, + ReviewStepComponent, + EXPERIMENTAL_TemplateOutputsComponent: TemplateOutputsComponent, + EXPERIMENTAL_TemplateListPageComponent: + TemplateListPageComponent = TemplateListPage, + EXPERIMENTAL_TemplateWizardPageComponent: + TemplateWizardPageComponent = TemplateWizardPage, } = {}, } = props; const outlet = useOutlet() || props.children; const customFieldExtensions = - useCustomFieldExtensions(outlet); + useCustomFieldExtensions(outlet); const fieldExtensions = [ ...customFieldExtensions, @@ -106,7 +114,7 @@ export const Router = (props: PropsWithChildren) => { customFieldExtension => customFieldExtension.name === name, ), ), - ] as NextFieldExtensionOptions[]; + ] as FieldExtensionOptions[]; const customLayouts = useCustomLayouts(outlet); @@ -130,7 +138,8 @@ export const Router = (props: PropsWithChildren) => { } diff --git a/plugins/scaffolder/src/next/Router/index.ts b/plugins/scaffolder/src/components/Router/index.ts similarity index 87% rename from plugins/scaffolder/src/next/Router/index.ts rename to plugins/scaffolder/src/components/Router/index.ts index dac1db7b3a..afe51fbdc9 100644 --- a/plugins/scaffolder/src/next/Router/index.ts +++ b/plugins/scaffolder/src/components/Router/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { Router } from './Router'; -export type { NextRouterProps } from './Router'; +export { Router, type RouterProps } from './Router'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/CustomFieldExplorer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/CustomFieldExplorer.tsx deleted file mode 100644 index e470267b23..0000000000 --- a/plugins/scaffolder/src/components/TemplateEditorPage/CustomFieldExplorer.tsx +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2022 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. - */ -import { StreamLanguage } from '@codemirror/language'; -import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import { - Button, - Card, - CardContent, - CardHeader, - FormControl, - IconButton, - InputLabel, - makeStyles, - MenuItem, - Select, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import { ISubmitEvent, withTheme } from '@rjsf/core'; -import { Theme as MuiTheme } from '@rjsf/material-ui'; -import CodeMirror from '@uiw/react-codemirror'; -import React, { useCallback, useMemo, useState } from 'react'; -import yaml from 'yaml'; -import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; -import * as fieldOverrides from '../MultistepJsonForm/FieldOverrides'; -import { TemplateEditorForm } from './TemplateEditorForm'; - -const Form = withTheme(MuiTheme); - -const useStyles = makeStyles(theme => ({ - root: { - gridArea: 'pageContent', - display: 'grid', - gridTemplateAreas: ` - "controls controls" - "fieldForm preview" - `, - gridTemplateRows: 'auto 1fr', - gridTemplateColumns: '1fr 1fr', - }, - controls: { - gridArea: 'controls', - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - margin: theme.spacing(1), - }, - fieldForm: { - gridArea: 'fieldForm', - }, - preview: { - gridArea: 'preview', - }, -})); - -export const CustomFieldExplorer = ({ - customFieldExtensions = [], - onClose, -}: { - customFieldExtensions?: FieldExtensionOptions[]; - onClose?: () => void; -}) => { - const classes = useStyles(); - const fieldOptions = customFieldExtensions.filter(field => !!field.schema); - const [selectedField, setSelectedField] = useState(fieldOptions[0]); - const [fieldFormState, setFieldFormState] = useState({}); - const [formState, setFormState] = useState({}); - const [refreshKey, setRefreshKey] = useState(Date.now()); - const sampleFieldTemplate = useMemo( - () => - yaml.stringify({ - parameters: [ - { - title: `${selectedField.name} Example`, - properties: { - [selectedField.name]: { - type: selectedField.schema?.returnValue?.type, - 'ui:field': selectedField.name, - 'ui:options': fieldFormState, - }, - }, - }, - ], - }), - [fieldFormState, selectedField], - ); - - const fieldComponents = useMemo(() => { - return Object.fromEntries( - customFieldExtensions.map(({ name, component }) => [name, component]), - ); - }, [customFieldExtensions]); - - const handleSelectionChange = useCallback( - (selection: FieldExtensionOptions) => { - setSelectedField(selection); - setFieldFormState({}); - setFormState({}); - }, - [setFieldFormState, setFormState, setSelectedField], - ); - - const handleFieldConfigChange = useCallback( - (state: {}) => { - setFieldFormState(state); - setFormState({}); - // Force TemplateEditorForm to re-render since some fields - // may not be responsive to ui:option changes - setRefreshKey(Date.now()); - }, - [setFieldFormState, setRefreshKey], - ); - - return ( -
-
- - - Choose Custom Field Extension - - - - - - - -
-
- - - -
) => - handleFieldConfigChange(e.formData) - } - schema={selectedField.schema?.uiOptions || {}} - > - -
-
-
-
-
- - - - - - - null} - /> -
-
- ); -}; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx deleted file mode 100644 index e07434aff7..0000000000 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2022 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. - */ -import { makeStyles } from '@material-ui/core'; -import React, { useState } from 'react'; -import type { - FieldExtensionOptions, - LayoutOptions, -} from '@backstage/plugin-scaffolder-react'; -import { TemplateDirectoryAccess } from '../../lib/filesystem'; -import { DirectoryEditorProvider } from './DirectoryEditorContext'; -import { DryRunProvider } from './DryRunContext'; -import { DryRunResults } from './DryRunResults'; -import { TemplateEditorBrowser } from './TemplateEditorBrowser'; -import { TemplateEditorForm } from './TemplateEditorForm'; -import { TemplateEditorTextArea } from './TemplateEditorTextArea'; - -const useStyles = makeStyles({ - // Reset and fix sizing to make sure scrolling behaves correctly - root: { - gridArea: 'pageContent', - - display: 'grid', - gridTemplateAreas: ` - "browser editor preview" - "results results results" - `, - gridTemplateColumns: '1fr 3fr 2fr', - gridTemplateRows: '1fr auto', - }, - browser: { - gridArea: 'browser', - overflow: 'auto', - }, - editor: { - gridArea: 'editor', - overflow: 'auto', - }, - preview: { - gridArea: 'preview', - overflow: 'auto', - }, - results: { - gridArea: 'results', - }, -}); - -export const TemplateEditor = (props: { - directory: TemplateDirectoryAccess; - fieldExtensions?: FieldExtensionOptions[]; - layouts?: LayoutOptions[]; - onClose?: () => void; -}) => { - const classes = useStyles(); - - const [errorText, setErrorText] = useState(); - - return ( - - -
-
- -
-
- -
-
- -
-
- -
-
-
-
- ); -}; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx deleted file mode 100644 index 2a726110b8..0000000000 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2022 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. - */ -import { useApiHolder } from '@backstage/core-plugin-api'; -import { JsonObject, JsonValue } from '@backstage/types'; -import { makeStyles } from '@material-ui/core/styles'; -import React, { Component, ReactNode, useMemo, useState } from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; -import yaml from 'yaml'; -import type { - FieldExtensionOptions, - LayoutOptions, - TemplateParameterSchema, -} from '@backstage/plugin-scaffolder-react'; -import { MultistepJsonForm } from '../MultistepJsonForm'; -import { createValidator } from '../TemplatePage'; -import { useDirectoryEditor } from './DirectoryEditorContext'; -import { useDryRun } from './DryRunContext'; - -const useStyles = makeStyles({ - containerWrapper: { - position: 'relative', - width: '100%', - height: '100%', - }, - container: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - overflow: 'auto', - }, -}); - -interface ErrorBoundaryProps { - invalidator: unknown; - setErrorText(errorText: string | undefined): void; - children: ReactNode; -} - -interface ErrorBoundaryState { - shouldRender: boolean; -} - -class ErrorBoundary extends Component { - state = { - shouldRender: true, - }; - - componentDidUpdate(prevProps: { invalidator: unknown }) { - if (prevProps.invalidator !== this.props.invalidator) { - this.setState({ shouldRender: true }); - } - } - - componentDidCatch(error: Error) { - this.props.setErrorText(error.message); - this.setState({ shouldRender: false }); - } - - render() { - return this.state.shouldRender ? this.props.children : null; - } -} - -interface TemplateEditorFormProps { - content?: string; - /** Setting this to true will cause the content to be parsed as if it is the template entity spec */ - contentIsSpec?: boolean; - data: JsonObject; - onUpdate: (data: JsonObject) => void; - setErrorText: (errorText?: string) => void; - - onDryRun?: (data: JsonObject) => Promise; - fieldExtensions?: FieldExtensionOptions[]; - layouts?: LayoutOptions[]; -} - -function isJsonObject(value: JsonValue | undefined): value is JsonObject { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -/** Shows the a template form that is parsed from the provided content */ -export function TemplateEditorForm(props: TemplateEditorFormProps) { - const { - content, - contentIsSpec, - data, - onUpdate, - onDryRun, - setErrorText, - fieldExtensions = [], - layouts = [], - } = props; - const classes = useStyles(); - const apiHolder = useApiHolder(); - - const [steps, setSteps] = useState(); - - const fields = useMemo(() => { - return Object.fromEntries( - fieldExtensions.map(({ name, component }) => [name, component]), - ); - }, [fieldExtensions]); - - useDebounce( - () => { - try { - if (!content) { - setSteps(undefined); - return; - } - const parsed: JsonValue = yaml - .parseAllDocuments(content) - .filter(c => c) - .map(c => c.toJSON())[0]; - - if (!isJsonObject(parsed)) { - setSteps(undefined); - return; - } - - let rootObj = parsed; - if (!contentIsSpec) { - const isTemplate = - String(parsed.kind).toLocaleLowerCase('en-US') === 'template'; - if (!isTemplate) { - setSteps(undefined); - return; - } - - rootObj = isJsonObject(parsed.spec) ? parsed.spec : {}; - } - - const { parameters } = rootObj; - - if (!Array.isArray(parameters)) { - setErrorText('Template parameters must be an array'); - setSteps(undefined); - return; - } - - const fieldValidators = Object.fromEntries( - fieldExtensions.map(({ name, validation }) => [name, validation]), - ); - - setErrorText(); - setSteps( - parameters.flatMap(param => - isJsonObject(param) - ? [ - { - title: String(param.title), - schema: param, - validate: createValidator(param, fieldValidators, { - apiHolder, - }), - }, - ] - : [], - ), - ); - } catch (e) { - setErrorText(e.message); - } - }, - 250, - [contentIsSpec, content, apiHolder], - ); - - if (!steps) { - return null; - } - - return ( -
-
- - onUpdate(e.formData)} - onReset={() => onUpdate({})} - finishButtonLabel={onDryRun && 'Try It'} - onFinish={onDryRun && (() => onDryRun(data))} - layouts={layouts} - /> - -
-
- ); -} - -/** A version of the TemplateEditorForm that is connected to the DirectoryEditor and DryRun contexts */ -export function TemplateEditorFormDirectoryEditorDryRun( - props: Pick< - TemplateEditorFormProps, - 'setErrorText' | 'fieldExtensions' | 'layouts' - >, -) { - const { setErrorText, fieldExtensions = [], layouts } = props; - const dryRun = useDryRun(); - - const directoryEditor = useDirectoryEditor(); - const { selectedFile } = directoryEditor; - - const [data, setData] = useState({}); - - const handleDryRun = async () => { - if (!selectedFile) { - return; - } - - try { - await dryRun.execute({ - templateContent: selectedFile.content, - values: data, - files: directoryEditor.files, - }); - setErrorText(); - } catch (e) { - setErrorText(String(e.cause || e)); - throw e; - } - }; - - const content = - selectedFile && selectedFile.path.match(/\.ya?ml$/) - ? selectedFile.content - : undefined; - - return ( - - ); -} - -TemplateEditorForm.DirectoryEditorDryRun = - TemplateEditorFormDirectoryEditorDryRun; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.test.tsx deleted file mode 100644 index bf9cd8c464..0000000000 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 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. - */ - -import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { TemplateEditorPage } from './TemplateEditorPage'; - -describe('TemplateEditorPage', () => { - it('renders without exploding', async () => { - await renderInTestApp(); - - expect(screen.getByText('Load Template Directory')).toBeInTheDocument(); - expect(screen.getByText('Edit Template Form')).toBeInTheDocument(); - }); - - it('template directory loading should not be supported in Jest', async () => { - await renderInTestApp(); - - expect( - screen.getByRole('button', { name: /Load Template Directory/ }), - ).toBeDisabled(); - }); - - it('should be able to continue to form preview', async () => { - await renderInTestApp( - - - , - ); - - await userEvent.click(screen.getByText('Edit Template Form')); - - expect(screen.getByLabelText('Load Existing Template')).toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx deleted file mode 100644 index 9442394177..0000000000 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2022 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. - */ -import { Entity } from '@backstage/catalog-model'; -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { - catalogApiRef, - humanizeEntityRef, -} from '@backstage/plugin-catalog-react'; -import { - FormControl, - IconButton, - InputLabel, - LinearProgress, - makeStyles, - MenuItem, - Select, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import React, { useCallback, useState } from 'react'; -import useAsync from 'react-use/lib/useAsync'; -import yaml from 'yaml'; -import { - type FieldExtensionOptions, - type LayoutOptions, -} from '@backstage/plugin-scaffolder-react'; -import { TemplateEditorForm } from './TemplateEditorForm'; -import { TemplateEditorTextArea } from './TemplateEditorTextArea'; - -const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI -parameters: - - title: Fill in some steps - required: - - name - properties: - name: - title: Name - type: string - description: Unique name of the component - owner: - title: Owner - type: string - description: Owner of the component - ui:field: OwnerPicker - ui:options: - catalogFilter: - kind: Group - - title: Choose a location - required: - - repoUrl - properties: - repoUrl: - title: Repository Location - type: string - ui:field: RepoUrlPicker - ui:options: - allowedHosts: - - github.com -steps: - - id: fetch-base - name: Fetch Base - action: fetch:template - input: - url: ./template - values: - name: \${{parameters.name}} -`; - -type TemplateOption = { - label: string; - value: Entity; -}; - -const useStyles = makeStyles(theme => ({ - root: { - gridArea: 'pageContent', - display: 'grid', - gridTemplateAreas: ` - "controls controls" - "textArea preview" - `, - gridTemplateRows: 'auto 1fr', - gridTemplateColumns: '1fr 1fr', - }, - controls: { - gridArea: 'controls', - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - margin: theme.spacing(1), - }, - textArea: { - gridArea: 'textArea', - }, - preview: { - gridArea: 'preview', - }, -})); - -export const TemplateFormPreviewer = ({ - defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, - customFieldExtensions = [], - onClose, - layouts = [], -}: { - defaultPreviewTemplate?: string; - customFieldExtensions?: FieldExtensionOptions[]; - onClose?: () => void; - layouts?: LayoutOptions[]; -}) => { - const classes = useStyles(); - const alertApi = useApi(alertApiRef); - const catalogApi = useApi(catalogApiRef); - const [selectedTemplate, setSelectedTemplate] = useState(''); - const [errorText, setErrorText] = useState(); - const [templateOptions, setTemplateOptions] = useState([]); - const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate); - const [formState, setFormState] = useState({}); - - const { loading } = useAsync( - () => - catalogApi - .getEntities({ - filter: { kind: 'template' }, - fields: [ - 'kind', - 'metadata.namespace', - 'metadata.name', - 'metadata.title', - 'spec.parameters', - 'spec.steps', - 'spec.output', - ], - }) - .then(({ items }) => - setTemplateOptions( - items.map(template => ({ - label: - template.metadata.title ?? - humanizeEntityRef(template, { defaultKind: 'template' }), - value: template, - })), - ), - ) - .catch(e => - alertApi.post({ - message: `Error loading exisiting templates: ${e.message}`, - severity: 'error', - }), - ), - [catalogApi], - ); - - const handleSelectChange = useCallback( - // TODO(Rugvip): Afaik this should be Entity, but didn't want to make runtime changes while fixing types - (selected: any) => { - setSelectedTemplate(selected); - setTemplateYaml(yaml.stringify(selected.spec)); - }, - [setTemplateYaml], - ); - - return ( - <> - {loading && } -
-
- - - Load Existing Template - - - - - - - -
-
- -
-
- -
-
- - ); -}; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts index d116bef228..9302e9abc1 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { KubernetesValidatorFunctions } from '@backstage/catalog-model'; export const entityNamePickerValidation = ( diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index cbc587a732..300e23ee89 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -18,11 +18,12 @@ import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { FieldProps } from '@rjsf/core'; + import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; import { EntityPicker } from './EntityPicker'; import { EntityPickerProps } from './schema'; +import { FieldProps } from '@rjsf/utils'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts index 99847f9e3b..55ba92abdc 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { ApiHolder } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; diff --git a/plugins/scaffolder/src/components/index.ts b/plugins/scaffolder/src/components/index.ts index a5c17b8ab0..fd8c11e2da 100644 --- a/plugins/scaffolder/src/components/index.ts +++ b/plugins/scaffolder/src/components/index.ts @@ -15,7 +15,12 @@ */ export * from './fields'; export type { RepoUrlPickerUiOptions } from './fields'; + export { TemplateTypePicker } from './TemplateTypePicker'; -export { TaskPage, type TaskPageProps } from './TaskPage'; + export type { RouterProps } from './Router'; -export type { ReviewStepProps } from './types'; +export { OngoingTask as TaskPage } from './OngoingTask'; + +export type { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; + +export type { TaskPageProps } from '../legacy/TaskPage'; diff --git a/plugins/scaffolder/src/components/types.ts b/plugins/scaffolder/src/components/types.ts index f5989a2ae1..b1e99a84e6 100644 --- a/plugins/scaffolder/src/components/types.ts +++ b/plugins/scaffolder/src/components/types.ts @@ -13,25 +13,3 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { UiSchema } from '@rjsf/utils'; -import { JsonObject } from '@backstage/types'; - -/** - * 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; - }[]; -}; diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx b/plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/DescriptionField.tsx similarity index 100% rename from plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/DescriptionField.tsx rename to plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/DescriptionField.tsx diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/index.ts b/plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/index.ts similarity index 100% rename from plugins/scaffolder/src/components/MultistepJsonForm/FieldOverrides/index.ts rename to plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/index.ts diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.test.tsx rename to plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.test.tsx diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.tsx similarity index 98% rename from plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx rename to plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.tsx index d8299a9b5f..eddbbff4bf 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.tsx @@ -35,11 +35,13 @@ import React, { ComponentType, useState } from 'react'; import { transformSchemaToProps } from './schema'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; -import { ReviewStepProps } from '../types'; import { ReviewStep } from './ReviewStep'; import { extractSchemaFromStep } from '@backstage/plugin-scaffolder-react/alpha'; import { selectedTemplateRouteRef } from '../../routes'; -import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; +import { + LayoutOptions, + ReviewStepProps, +} from '@backstage/plugin-scaffolder-react'; const Form = withTheme(MuiTheme); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx b/plugins/scaffolder/src/legacy/MultistepJsonForm/ReviewStep.tsx similarity index 98% rename from plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx rename to plugins/scaffolder/src/legacy/MultistepJsonForm/ReviewStep.tsx index 112b96bdf1..7d9e81d91b 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/ReviewStep.tsx +++ b/plugins/scaffolder/src/legacy/MultistepJsonForm/ReviewStep.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; import { UiSchema } from '@rjsf/core'; import { JsonObject } from '@backstage/types'; -import { ReviewStepProps } from '../types'; +import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; export function getReviewData( formData: Record, diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/index.ts b/plugins/scaffolder/src/legacy/MultistepJsonForm/index.ts similarity index 100% rename from plugins/scaffolder/src/components/MultistepJsonForm/index.ts rename to plugins/scaffolder/src/legacy/MultistepJsonForm/index.ts diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/schema.test.ts b/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.test.ts similarity index 100% rename from plugins/scaffolder/src/components/MultistepJsonForm/schema.test.ts rename to plugins/scaffolder/src/legacy/MultistepJsonForm/schema.test.ts diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/schema.ts b/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.ts similarity index 100% rename from plugins/scaffolder/src/components/MultistepJsonForm/schema.ts rename to plugins/scaffolder/src/legacy/MultistepJsonForm/schema.ts diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/legacy/Router.tsx similarity index 91% rename from plugins/scaffolder/src/components/Router.tsx rename to plugins/scaffolder/src/legacy/Router.tsx index 3ceda359cc..ac183b9470 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/legacy/Router.tsx @@ -21,18 +21,17 @@ import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { ScaffolderPage } from './ScaffolderPage'; import { TemplatePage } from './TemplatePage'; import { TaskPage } from './TaskPage'; -import { ActionsPage } from './ActionsPage'; -import { TemplateEditorPage } from './TemplateEditorPage'; +import { ActionsPage } from '../components/ActionsPage'; import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../extensions/default'; import { useRouteRef, useRouteRefParams } from '@backstage/core-plugin-api'; +import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; import { - FieldExtensionOptions, + ReviewStepProps, SecretsContextProvider, useCustomFieldExtensions, useCustomLayouts, } from '@backstage/plugin-scaffolder-react'; -import { ListTasksPage } from './ListTasksPage'; -import { ReviewStepProps } from './types'; +import { ListTasksPage } from '../components/ListTasksPage'; import { actionsRouteRef, editRouteRef, @@ -41,12 +40,13 @@ import { scaffolderTaskRouteRef, selectedTemplateRouteRef, } from '../routes'; +import { TemplateEditorPage } from './TemplateEditorPage'; /** * The props for the entrypoint `ScaffolderPage` component the plugin. - * @public + * @alpha */ -export type RouterProps = { +export type LegacyRouterProps = { components?: { ReviewStepComponent?: ComponentType; TemplateCardComponent?: @@ -77,11 +77,11 @@ export type RouterProps = { }; /** - * The main entrypoint `Router` for the `ScaffolderPlugin`. + * The legacy router * - * @public + * @alpha */ -export const Router = (props: RouterProps) => { +export const LegacyRouter = (props: LegacyRouterProps) => { const { groups, templateFilter, @@ -95,7 +95,9 @@ export const Router = (props: RouterProps) => { const outlet = useOutlet(); const TaskPageElement = TaskPageComponent ?? TaskPage; - const customFieldExtensions = useCustomFieldExtensions(outlet); + const customFieldExtensions = + useCustomFieldExtensions(outlet); + const fieldExtensions = [ ...customFieldExtensions, ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter( @@ -104,7 +106,7 @@ export const Router = (props: RouterProps) => { customFieldExtension => customFieldExtension.name === name, ), ), - ] as FieldExtensionOptions[]; + ] as LegacyFieldExtensionOptions[]; const customLayouts = useCustomLayouts(outlet); diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPage.tsx similarity index 98% rename from plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx rename to plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPage.tsx index 40435573e9..54122eddf0 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPage.tsx @@ -34,11 +34,11 @@ import { } from '@backstage/plugin-catalog-react'; import React, { ComponentType } from 'react'; import { TemplateList } from '../TemplateList'; -import { TemplateTypePicker } from '../TemplateTypePicker'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; import { usePermission } from '@backstage/plugin-permission-react'; import { ScaffolderPageContextMenu } from './ScaffolderPageContextMenu'; import { registerComponentRouteRef } from '../../routes'; +import { TemplateTypePicker } from '../../components'; export type ScaffolderPageProps = { TemplateCardComponent?: diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPageContextMenu.test.tsx b/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPageContextMenu.test.tsx rename to plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.test.tsx diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPageContextMenu.tsx b/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.tsx similarity index 100% rename from plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPageContextMenu.tsx rename to plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.tsx diff --git a/plugins/scaffolder/src/components/ScaffolderPage/index.ts b/plugins/scaffolder/src/legacy/ScaffolderPage/index.ts similarity index 100% rename from plugins/scaffolder/src/components/ScaffolderPage/index.ts rename to plugins/scaffolder/src/legacy/ScaffolderPage/index.ts diff --git a/plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx b/plugins/scaffolder/src/legacy/TaskPage/IconLink.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx rename to plugins/scaffolder/src/legacy/TaskPage/IconLink.test.tsx diff --git a/plugins/scaffolder/src/components/TaskPage/IconLink.tsx b/plugins/scaffolder/src/legacy/TaskPage/IconLink.tsx similarity index 100% rename from plugins/scaffolder/src/components/TaskPage/IconLink.tsx rename to plugins/scaffolder/src/legacy/TaskPage/IconLink.tsx diff --git a/plugins/scaffolder/src/components/TaskPage/TaskErrors.tsx b/plugins/scaffolder/src/legacy/TaskPage/TaskErrors.tsx similarity index 100% rename from plugins/scaffolder/src/components/TaskPage/TaskErrors.tsx rename to plugins/scaffolder/src/legacy/TaskPage/TaskErrors.tsx diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/legacy/TaskPage/TaskPage.tsx similarity index 99% rename from plugins/scaffolder/src/components/TaskPage/TaskPage.tsx rename to plugins/scaffolder/src/legacy/TaskPage/TaskPage.tsx index 78cdc5d935..7ba7c2c9f7 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/legacy/TaskPage/TaskPage.tsx @@ -237,7 +237,7 @@ const hasLinks = ({ links = [] }: ScaffolderTaskOutput): boolean => * TaskPageProps for constructing a TaskPage * @param loadingText - Optional loading text shown before a task begins executing. * - * @public + * @deprecated - this is a useless type that is no longer used. */ export type TaskPageProps = { loadingText?: string; @@ -246,7 +246,7 @@ export type TaskPageProps = { /** * TaskPage for showing the status of the taskId provided as a param * - * @public + * @alpha */ export const TaskPage = (props: TaskPageProps) => { const { loadingText } = props; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx b/plugins/scaffolder/src/legacy/TaskPage/TaskPageLinks.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx rename to plugins/scaffolder/src/legacy/TaskPage/TaskPageLinks.test.tsx diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/legacy/TaskPage/TaskPageLinks.tsx similarity index 100% rename from plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx rename to plugins/scaffolder/src/legacy/TaskPage/TaskPageLinks.tsx diff --git a/plugins/scaffolder/src/components/TaskPage/index.ts b/plugins/scaffolder/src/legacy/TaskPage/index.ts similarity index 100% rename from plugins/scaffolder/src/components/TaskPage/index.ts rename to plugins/scaffolder/src/legacy/TaskPage/index.ts diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/legacy/TemplateCard/TemplateCard.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx rename to plugins/scaffolder/src/legacy/TemplateCard/TemplateCard.tsx diff --git a/plugins/scaffolder/src/components/TemplateCard/index.ts b/plugins/scaffolder/src/legacy/TemplateCard/index.ts similarity index 100% rename from plugins/scaffolder/src/components/TemplateCard/index.ts rename to plugins/scaffolder/src/legacy/TemplateCard/index.ts diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorPage.tsx similarity index 82% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx rename to plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorPage.tsx index ebb763e145..e65869ad0e 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorPage.tsx @@ -19,14 +19,12 @@ import { TemplateDirectoryAccess, WebFileSystemAccess, } from '../../lib/filesystem'; -import { CustomFieldExplorer } from './CustomFieldExplorer'; -import { TemplateEditorIntro } from './TemplateEditorIntro'; -import { TemplateEditor } from './TemplateEditor'; -import { TemplateFormPreviewer } from './TemplateFormPreviewer'; -import { - type FieldExtensionOptions, - type LayoutOptions, -} from '@backstage/plugin-scaffolder-react'; +import { type LayoutOptions } from '@backstage/plugin-scaffolder-react'; +import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; +import { CustomFieldExplorer } from '../../next/TemplateEditorPage/CustomFieldExplorer'; +import { TemplateFormPreviewer } from '../../next/TemplateEditorPage/TemplateFormPreviewer'; +import { TemplateEditor } from '../../next/TemplateEditorPage/TemplateEditor'; +import { TemplateEditorIntro } from '../../next/TemplateEditorPage/TemplateEditorIntro'; type Selection = | { @@ -42,7 +40,7 @@ type Selection = interface TemplateEditorPageProps { defaultPreviewTemplate?: string; - customFieldExtensions?: FieldExtensionOptions[]; + customFieldExtensions?: LegacyFieldExtensionOptions[]; layouts?: LayoutOptions[]; } diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/index.ts b/plugins/scaffolder/src/legacy/TemplateEditorPage/index.ts similarity index 93% rename from plugins/scaffolder/src/components/TemplateEditorPage/index.ts rename to plugins/scaffolder/src/legacy/TemplateEditorPage/index.ts index 7ec6bddb64..7de0d3c679 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/index.ts +++ b/plugins/scaffolder/src/legacy/TemplateEditorPage/index.ts @@ -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. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - export { TemplateEditorPage } from './TemplateEditorPage'; diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.test.tsx b/plugins/scaffolder/src/legacy/TemplateList/TemplateList.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateList/TemplateList.test.tsx rename to plugins/scaffolder/src/legacy/TemplateList/TemplateList.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx b/plugins/scaffolder/src/legacy/TemplateList/TemplateList.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateList/TemplateList.tsx rename to plugins/scaffolder/src/legacy/TemplateList/TemplateList.tsx diff --git a/plugins/scaffolder/src/components/TemplateList/index.ts b/plugins/scaffolder/src/legacy/TemplateList/index.ts similarity index 100% rename from plugins/scaffolder/src/components/TemplateList/index.ts rename to plugins/scaffolder/src/legacy/TemplateList/index.ts diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx rename to plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.test.tsx diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.tsx similarity index 97% rename from plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx rename to plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.tsx index c615649f93..a5cac1ef54 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.tsx @@ -20,10 +20,10 @@ import React, { ComponentType, useCallback, useState } from 'react'; import { Navigate, useNavigate } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { - type FieldExtensionOptions, type LayoutOptions, scaffolderApiRef, useTemplateSecrets, + ReviewStepProps, } from '@backstage/plugin-scaffolder-react'; import { MultistepJsonForm } from '../MultistepJsonForm'; import { createValidator } from './createValidator'; @@ -38,12 +38,12 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; -import { ReviewStepProps } from '../types'; import { rootRouteRef, scaffolderTaskRouteRef, selectedTemplateRouteRef, } from '../../routes'; +import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); @@ -56,7 +56,7 @@ const useTemplateParameterSchema = (templateRef: string) => { type Props = { ReviewStepComponent?: ComponentType; - customFieldExtensions?: FieldExtensionOptions[]; + customFieldExtensions?: LegacyFieldExtensionOptions[]; layouts?: LayoutOptions[]; headerOptions?: { pageTitleOverride?: string; diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts b/plugins/scaffolder/src/legacy/TemplatePage/createValidator.test.ts similarity index 73% rename from plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts rename to plugins/scaffolder/src/legacy/TemplatePage/createValidator.test.ts index 1858e40241..117b54b0d2 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.test.ts +++ b/plugins/scaffolder/src/legacy/TemplatePage/createValidator.test.ts @@ -15,7 +15,7 @@ */ import { createValidator } from './createValidator'; -import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react'; +import { LegacyCustomFieldValidator } from '@backstage/plugin-scaffolder-react/alpha'; import { ApiHolder } from '@backstage/core-plugin-api'; import { FieldValidation, FormValidation } from '@rjsf/core'; @@ -26,49 +26,51 @@ type CustomLinkType = { }; describe('createValidator', () => { - const validators: Record> = - { - CustomPicker: ( - value: unknown, - fieldValidation: FieldValidation, - _context: { apiHolder: ApiHolder }, - ) => { - if (!value || !(value as { value?: unknown }).value) { - fieldValidation.addError('Error !'); - } - }, - CustomLink: ( - values: unknown, - fieldValidation: FieldValidation, - _context: { apiHolder: ApiHolder }, - ) => { - const input = values as CustomLinkType[]; - for (const item of input) { - const validGitlabUrlRegex = - /gitlab\.(?:stg\.)?spotify\.com\?owner=.*&repo=.*/; + const validators: Record< + string, + undefined | LegacyCustomFieldValidator + > = { + CustomPicker: ( + value: unknown, + fieldValidation: FieldValidation, + _context: { apiHolder: ApiHolder }, + ) => { + if (!value || !(value as { value?: unknown }).value) { + fieldValidation.addError('Error !'); + } + }, + CustomLink: ( + values: unknown, + fieldValidation: FieldValidation, + _context: { apiHolder: ApiHolder }, + ) => { + const input = values as CustomLinkType[]; + for (const item of input) { + const validGitlabUrlRegex = + /gitlab\.(?:stg\.)?spotify\.com\?owner=.*&repo=.*/; - if (!item || !validGitlabUrlRegex.test(item.url)) { - fieldValidation.addError( - `Make sure to put in a valid gitlab clone url.`, - ); - } + if (!item || !validGitlabUrlRegex.test(item.url)) { + fieldValidation.addError( + `Make sure to put in a valid gitlab clone url.`, + ); } - }, - TagPicker: ( - values: unknown, - fieldValidation: FieldValidation, - _context: { apiHolder: ApiHolder }, - ) => { - const input = values as string[]; - for (const item of input) { - if (!/^[a-z0-9-]+$/.test(item)) { - fieldValidation.addError( - 'A tag name can only contain lowercase letters, numeric characters or dashes', - ); - } + } + }, + TagPicker: ( + values: unknown, + fieldValidation: FieldValidation, + _context: { apiHolder: ApiHolder }, + ) => { + const input = values as string[]; + for (const item of input) { + if (!/^[a-z0-9-]+$/.test(item)) { + fieldValidation.addError( + 'A tag name can only contain lowercase letters, numeric characters or dashes', + ); } - }, - }; + } + }, + }; const apiHolderMock: jest.Mocked = { get: jest.fn().mockImplementation(() => { diff --git a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts b/plugins/scaffolder/src/legacy/TemplatePage/createValidator.ts similarity index 95% rename from plugins/scaffolder/src/components/TemplatePage/createValidator.ts rename to plugins/scaffolder/src/legacy/TemplatePage/createValidator.ts index 8bfe8c8313..04325ce5e5 100644 --- a/plugins/scaffolder/src/components/TemplatePage/createValidator.ts +++ b/plugins/scaffolder/src/legacy/TemplatePage/createValidator.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react'; +import { LegacyCustomFieldValidator } from '@backstage/plugin-scaffolder-react/alpha'; import { FormValidation } from '@rjsf/core'; import { JsonObject, JsonValue } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; @@ -29,7 +29,7 @@ function isArray(obj: unknown): obj is JsonObject { export const createValidator = ( rootSchema: JsonObject, - validators: Record>, + validators: Record>, context: { apiHolder: ApiHolder; }, diff --git a/plugins/scaffolder/src/components/TemplatePage/index.ts b/plugins/scaffolder/src/legacy/TemplatePage/index.ts similarity index 100% rename from plugins/scaffolder/src/components/TemplatePage/index.ts rename to plugins/scaffolder/src/legacy/TemplatePage/index.ts diff --git a/plugins/scaffolder/src/legacy/index.ts b/plugins/scaffolder/src/legacy/index.ts new file mode 100644 index 0000000000..83b5717fbe --- /dev/null +++ b/plugins/scaffolder/src/legacy/index.ts @@ -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 { LegacyRouter, type LegacyRouterProps } from './Router'; diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx index 15298dff8a..af195b866a 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx @@ -31,12 +31,10 @@ import CloseIcon from '@material-ui/icons/Close'; import CodeMirror from '@uiw/react-codemirror'; import React, { useCallback, useMemo, useState } from 'react'; import yaml from 'yaml'; -import { - NextFieldExtensionOptions, - Form, -} from '@backstage/plugin-scaffolder-react/alpha'; +import { Form } from '@backstage/plugin-scaffolder-react/alpha'; import { TemplateEditorForm } from './TemplateEditorForm'; import validator from '@rjsf/validator-ajv8'; +import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; const useStyles = makeStyles(theme => ({ root: { @@ -68,7 +66,7 @@ export const CustomFieldExplorer = ({ customFieldExtensions = [], onClose, }: { - customFieldExtensions?: NextFieldExtensionOptions[]; + customFieldExtensions?: FieldExtensionOptions[]; onClose?: () => void; }) => { const classes = useStyles(); @@ -102,7 +100,7 @@ export const CustomFieldExplorer = ({ }, [customFieldExtensions]); const handleSelectionChange = useCallback( - (selection: NextFieldExtensionOptions) => { + (selection: FieldExtensionOptions) => { setSelectedField(selection); setFieldFormState({}); }, @@ -131,7 +129,7 @@ export const CustomFieldExplorer = ({ label="Choose Custom Field Extension" labelId="select-field-label" onChange={e => - handleSelectionChange(e.target.value as NextFieldExtensionOptions) + handleSelectionChange(e.target.value as FieldExtensionOptions) } > {fieldOptions.map((option, idx) => ( @@ -152,6 +150,7 @@ export const CustomFieldExplorer = ({
[]; + fieldExtensions?: FieldExtensionOptions[]; layouts?: LayoutOptions[]; onClose?: () => void; }) => { diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx rename to plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx rename to plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx index 898487e200..15c53d6d81 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx @@ -22,13 +22,11 @@ import yaml from 'yaml'; import { LayoutOptions, TemplateParameterSchema, + FieldExtensionOptions, } from '@backstage/plugin-scaffolder-react'; -import { - NextFieldExtensionOptions, - Stepper, -} from '@backstage/plugin-scaffolder-react/alpha'; -import { useDryRun } from '../../components/TemplateEditorPage/DryRunContext'; -import { useDirectoryEditor } from '../../components/TemplateEditorPage/DirectoryEditorContext'; +import { Stepper } from '@backstage/plugin-scaffolder-react/alpha'; +import { useDryRun } from './DryRunContext'; +import { useDirectoryEditor } from './DirectoryEditorContext'; const useStyles = makeStyles({ containerWrapper: { @@ -83,7 +81,7 @@ interface TemplateEditorFormProps { contentIsSpec?: boolean; setErrorText: (errorText?: string) => void; onDryRun?: (data: JsonObject) => Promise; - fieldExtensions?: NextFieldExtensionOptions[]; + fieldExtensions?: FieldExtensionOptions[]; layouts?: LayoutOptions[]; } diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx rename to plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorIntro.tsx diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx index 9863e99c73..aab693e590 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx @@ -22,9 +22,11 @@ import { import { CustomFieldExplorer } from './CustomFieldExplorer'; import { TemplateEditor } from './TemplateEditor'; import { TemplateFormPreviewer } from './TemplateFormPreviewer'; -import { type LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { NextFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; -import { TemplateEditorIntro } from '../../components/TemplateEditorPage/TemplateEditorIntro'; +import { + FieldExtensionOptions, + type LayoutOptions, +} from '@backstage/plugin-scaffolder-react'; +import { TemplateEditorIntro } from './TemplateEditorIntro'; type Selection = | { @@ -40,7 +42,7 @@ type Selection = interface TemplateEditorPageProps { defaultPreviewTemplate?: string; - customFieldExtensions?: NextFieldExtensionOptions[]; + customFieldExtensions?: FieldExtensionOptions[]; layouts?: LayoutOptions[]; } diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorTextArea.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorTextArea.tsx rename to plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx index 75d8c511b1..c253bc0a5f 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -32,10 +32,12 @@ import CloseIcon from '@material-ui/icons/Close'; import React, { useCallback, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import yaml from 'yaml'; -import { type LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { NextFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; +import { + LayoutOptions, + FieldExtensionOptions, +} from '@backstage/plugin-scaffolder-react'; import { TemplateEditorForm } from './TemplateEditorForm'; -import { TemplateEditorTextArea } from '../../components/TemplateEditorPage/TemplateEditorTextArea'; +import { TemplateEditorTextArea } from './TemplateEditorTextArea'; const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI parameters: @@ -114,7 +116,7 @@ export const TemplateFormPreviewer = ({ layouts = [], }: { defaultPreviewTemplate?: string; - customFieldExtensions?: NextFieldExtensionOptions[]; + customFieldExtensions?: FieldExtensionOptions[]; onClose?: () => void; layouts?: LayoutOptions[]; }) => { diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx index 863d3009e1..44390bc03c 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -38,7 +38,6 @@ import { import { ScaffolderPageContextMenu, TemplateCategoryPicker, - TemplateGroupFilter, TemplateGroups, } from '@backstage/plugin-scaffolder-react/alpha'; @@ -52,6 +51,7 @@ import { viewTechDocRouteRef, } from '../../routes'; import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; +import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; /** * @alpha diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index a7133ae8c7..464e23a201 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -26,12 +26,10 @@ import { scaffolderApiRef, useTemplateSecrets, type LayoutOptions, + FieldExtensionOptions, + ReviewStepProps, } from '@backstage/plugin-scaffolder-react'; -import { - FormProps, - Workflow, - NextFieldExtensionOptions, -} from '@backstage/plugin-scaffolder-react/alpha'; +import { FormProps, Workflow } from '@backstage/plugin-scaffolder-react/alpha'; import { JsonValue } from '@backstage/types'; import { Header, Page } from '@backstage/core-components'; @@ -45,9 +43,12 @@ import { * @alpha */ export type TemplateWizardPageProps = { - customFieldExtensions: NextFieldExtensionOptions[]; + customFieldExtensions: FieldExtensionOptions[]; + components?: { + ReviewStepComponent?: React.ComponentType; + }; layouts?: LayoutOptions[]; - FormProps?: FormProps; + formProps?: FormProps; }; export const TemplateWizardPage = (props: TemplateWizardPageProps) => { @@ -90,9 +91,10 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { namespace={namespace} templateName={templateName} onCreate={onCreate} + components={props.components} onError={onError} extensions={props.customFieldExtensions} - FormProps={props.FormProps} + formProps={props.formProps} layouts={props.layouts} /> diff --git a/plugins/scaffolder/src/next/index.ts b/plugins/scaffolder/src/next/index.ts index 6cef3eaa03..2ae1b7b6c7 100644 --- a/plugins/scaffolder/src/next/index.ts +++ b/plugins/scaffolder/src/next/index.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './Router'; export * from './TemplateListPage'; export * from './TemplateWizardPage'; export * from './types'; diff --git a/plugins/scaffolder/src/plugin.tsx b/plugins/scaffolder/src/plugin.tsx index ad9e70d843..fdde81695d 100644 --- a/plugins/scaffolder/src/plugin.tsx +++ b/plugins/scaffolder/src/plugin.tsx @@ -217,10 +217,10 @@ export const EntityTagsPickerFieldExtension = scaffolderPlugin.provide( * @alpha * The Router and main entrypoint to the Alpha Scaffolder plugin. */ -export const NextScaffolderPage = scaffolderPlugin.provide( +export const LegacyScaffolderPage = scaffolderPlugin.provide( createRoutableExtension({ - name: 'NextScaffolderPage', - component: () => import('./next/Router').then(m => m.Router), + name: 'LegacyScaffolderPage', + component: () => import('./legacy/Router').then(m => m.LegacyRouter), mountPoint: rootRouteRef, }), );