feat: started some more work on getting this into shape
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -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';
|
||||
+30
-6
@@ -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';
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user