feat: implemented useFormHooks hook for calling hooks on create

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-10-11 18:00:26 +02:00
parent 189201ef4b
commit aee334c14c
13 changed files with 226 additions and 148 deletions
@@ -26,12 +26,16 @@ import { makeStyles } from '@material-ui/core/styles';
import { errorApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api';
import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema';
import { Stepper, type StepperProps } from '../Stepper/Stepper';
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
import {
SecretsContextProvider,
useTemplateSecrets,
} from '../../../secrets/SecretsContext';
import { useFilteredSchemaProperties } from '../../hooks/useFilteredSchemaProperties';
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
import { useTemplateTimeSavedMinutes } from '../../hooks/useTemplateTimeSaved';
import { JsonValue } from '@backstage/types';
import { ScaffolderFormHook } from '../../extensions';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { useFormHooks } from '../../../../../scaffolder/src/alpha/hooks/useFormHooks';
const useStyles = makeStyles({
markdown: {
@@ -53,7 +57,6 @@ export type WorkflowProps = {
description?: string;
namespace: string;
templateName: string;
formHooks?: ScaffolderFormHook[];
components?: {
ReviewStepComponent?: React.ComponentType<ReviewStepProps>;
};
@@ -74,7 +77,6 @@ export type WorkflowProps = {
export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
const { title, description, namespace, templateName, onCreate, ...props } =
workflowProps;
const analytics = useAnalytics();
const styles = useStyles();
const templateRef = stringifyEntityRef({
@@ -82,17 +84,34 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
namespace: namespace,
name: templateName,
});
const errorApi = useApi(errorApiRef);
const { loading, manifest, error } = useTemplateParameterSchema(templateRef);
const sortedManifest = useFilteredSchemaProperties(manifest);
const minutesSaved = useTemplateTimeSavedMinutes(templateRef);
const { setSecrets } = useTemplateSecrets();
const formHooks = useFormHooks();
const workflowOnCreate = useCallback(
async (formState: Record<string, JsonValue>) => {
if (manifest?.EXPERIMENTAL_formHooks && formHooks?.size) {
// for each of the form hooks, go and call the hook with the context
await Promise.all(
manifest.EXPERIMENTAL_formHooks.map(async hook => {
const formHook = formHooks.get(hook.id);
if (!formHook) {
// eslint-disable-next-line no-console
console.error('Failed to find form hook', hook.id);
return;
}
await formHook.fn({
setSecrets,
input: hook.input,
});
}),
);
}
onCreate(formState);
const name =
@@ -101,7 +120,15 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
value: minutesSaved,
});
},
[onCreate, analytics, templateName, minutesSaved],
[
manifest?.EXPERIMENTAL_formHooks,
formHooks,
onCreate,
analytics,
templateName,
minutesSaved,
setSecrets,
],
);
useEffect(() => {
@@ -14,18 +14,17 @@
* limitations under the License.
*/
import { AnyApiRef } from '@backstage/core-plugin-api';
import { JsonValue } from '@backstage/types';
import { z } from 'zod';
export type ScaffolderFormHookContext<TInput> = {
input: TInput;
setSecret: (key: string, value: JsonValue) => void;
setSecrets: (input: Record<string, string>) => void;
};
export type ScaffolderInitialFormHook<
export type ScaffolderFormHook<
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = {},
TDeps extends { [key in string]: AnyApiRef } = { [key in string]: AnyApiRef },
TInput = {
TInput extends {} = {
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
},
> = {
@@ -43,11 +42,6 @@ export type ScaffolderInitialFormHook<
) => Promise<void>;
};
export type ScaffolderFormHook<TInput = any> = {
id: string;
fn: (ctx: ScaffolderFormHookContext<TInput>) => Promise<void>;
};
/**
* Method for creating hooks which can be used to collect
* secrets from the user before submitting to the backend.
@@ -56,7 +50,7 @@ export type ScaffolderFormHook<TInput = any> = {
export function createScaffolderFormHook<
TDeps extends { [key in string]: AnyApiRef },
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
TInput = {
TInput extends {} = {
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
},
>(options: {
@@ -71,7 +65,7 @@ export function createScaffolderFormHook<
? { [key in keyof TDeps]: TDeps[key]['T'] }
: never,
) => Promise<void>;
}): ScaffolderInitialFormHook<TInputSchema, TDeps, TInput> {
}): ScaffolderFormHook<TInputSchema, TDeps, TInput> {
return {
...options,
version: 'v1',
@@ -17,20 +17,23 @@
import useAsync from 'react-use/esm/useAsync';
import { scaffolderApiRef } from '../../api/ref';
import { useApi } from '@backstage/core-plugin-api';
import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react';
/**
* @alpha
*/
export const useTemplateParameterSchema = (templateRef: string) => {
const scaffolderApi = useApi(scaffolderApiRef);
const { value, loading, error } = useAsync(
const {
value: manifest,
loading,
error,
} = useAsync(
() => scaffolderApi.getTemplateParameterSchema(templateRef),
[scaffolderApi, templateRef],
);
return {
manifest: value as TemplateParameterSchema | undefined,
manifest,
loading,
error,
};
+1
View File
@@ -33,4 +33,5 @@ export type TemplateParameterSchema = {
description?: string;
schema: JsonObject;
}>;
EXPERIMENTAL_formHooks?: { id: string; input?: JsonObject }[];
};