chore: refactor, and move the execution

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-11-19 12:44:47 +01:00
parent f217918b14
commit a8140e10aa
8 changed files with 109 additions and 69 deletions
@@ -17,7 +17,7 @@ import { githubAuthApiRef } from '@backstage/core-plugin-api';
import { createScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
export const mockDecorator = createScaffolderFormDecorator({
id: 'githubOauth',
id: 'mock-decorator',
schema: {
input: {
test: z => z.string(),
@@ -26,8 +26,11 @@ export const mockDecorator = createScaffolderFormDecorator({
deps: {
githubApi: githubAuthApiRef,
},
decorator: async ({ setSecrets }, { githubApi }) => {
const token = await githubApi.getAccessToken();
setSecrets(state => ({ ...state, GITHUB_TOKEN: token }));
decorator: async (
{ setSecrets, setFormState, input: { test } },
{ githubApi: _githubApi },
) => {
setFormState(state => ({ ...state, test, mock: 'MOCK' }));
setSecrets(state => ({ ...state, GITHUB_TOKEN: 'MOCK_TOKEN' }));
},
});
@@ -120,11 +120,11 @@ export const Stepper = (stepperProps: StepperProps) => {
reviewButtonText = 'Review',
} = components;
const analytics = useAnalytics();
const { presentation, steps } = useTemplateSchema(props.manifest);
const apiHolder = useApiHolder();
const [activeStep, setActiveStep] = useState(0);
const [isValidating, setIsValidating] = useState(false);
const [initialState] = useFormDataFromQuery(props.initialState);
const { presentation, steps } = useTemplateSchema(props.manifest);
const [stepsState, setStepsState] =
useState<Record<string, JsonValue>>(initialState);
@@ -26,10 +26,7 @@ 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,
useInternalTemplateSecrets,
} from '../../../secrets/SecretsContext';
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
import { useFilteredSchemaProperties } from '../../hooks/useFilteredSchemaProperties';
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
import { useTemplateTimeSavedMinutes } from '../../hooks/useTemplateTimeSaved';
@@ -88,40 +85,9 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
const { loading, manifest, error } = useTemplateParameterSchema(templateRef);
const sortedManifest = useFilteredSchemaProperties(manifest);
const minutesSaved = useTemplateTimeSavedMinutes(templateRef);
const { setSecrets } = useInternalTemplateSecrets();
const formDecorators = useFormDecorators();
const workflowOnCreate = useCallback(
async (originalFormState: Record<string, JsonValue>) => {
let formState: Record<string, JsonValue> = { ...originalFormState };
if (manifest?.EXPERIMENTAL_formDecorators) {
// for each of the form decorators, go and call the decorator with the context
await Promise.all(
manifest.EXPERIMENTAL_formDecorators.map(async decorator => {
const formDecorator = formDecorators?.get(decorator.id);
if (!formDecorator) {
// eslint-disable-next-line no-console
console.error('Failed to find form decorator', decorator.id);
return;
}
await formDecorator.decorator({
setSecrets,
setFormState: (
handler: (
oldState: Record<string, JsonValue>,
) => Record<string, JsonValue>,
) => {
formState = { ...handler(formState) };
},
formState,
input: decorator.input ?? {},
});
}),
);
}
async (formState: Record<string, JsonValue>) => {
onCreate(formState);
const name =
@@ -130,15 +96,7 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
value: minutesSaved,
});
},
[
manifest?.EXPERIMENTAL_formDecorators,
formDecorators,
onCreate,
analytics,
templateName,
minutesSaved,
setSecrets,
],
[onCreate, analytics, templateName, minutesSaved],
);
useEffect(() => {
@@ -94,15 +94,3 @@ export const useTemplateSecrets = (): ScaffolderUseTemplateSecrets => {
return { setSecrets, secrets };
};
export const useInternalTemplateSecrets = () => {
const value = useContext(SecretsContext)?.atVersion(1);
if (!value) {
throw new Error(
'useTemplateSecrets must be used within a SecretsContextProvider',
);
}
return value;
};
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import React, { useState } from 'react';
import { Navigate, useNavigate } from 'react-router-dom';
import useAsync from 'react-use/esm/useAsync';
import {
@@ -36,9 +36,12 @@ import {
} from '@backstage/plugin-scaffolder-react';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { Workflow } from '@backstage/plugin-scaffolder-react/alpha';
import {
Workflow,
useTemplateParameterSchema,
} from '@backstage/plugin-scaffolder-react/alpha';
import { JsonValue } from '@backstage/types';
import { Header, Page } from '@backstage/core-components';
import { Header, Page, Progress } from '@backstage/core-components';
import {
rootRouteRef,
@@ -49,6 +52,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { scaffolderTranslationRef } from '../../../translation';
import { TemplateWizardPageContextMenu } from './TemplateWizardPageContextMenu';
import { useFormDecorators } from '../../hooks';
/**
* @alpha
@@ -70,9 +74,10 @@ export type TemplateWizardPageProps = {
export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
const rootRef = useRouteRef(rootRouteRef);
const taskRoute = useRouteRef(scaffolderTaskRouteRef);
const { secrets } = useTemplateSecrets();
const { secrets: contextSecrets } = useTemplateSecrets();
const scaffolderApi = useApi(scaffolderApiRef);
const catalogApi = useApi(catalogApiRef);
const [isCreating, setIsCreating] = useState(false);
const navigate = useNavigate();
const { templateName, namespace } = useRouteRefParams(
selectedTemplateRouteRef,
@@ -85,12 +90,26 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
name: templateName,
});
const { manifest } = useTemplateParameterSchema(templateRef);
const decorators = useFormDecorators({ manifest });
const { value: editUrl } = useAsync(async () => {
const data = await catalogApi.getEntityByRef(templateRef);
return data?.metadata.annotations?.[ANNOTATION_EDIT_URL];
}, [templateRef, catalogApi]);
const onCreate = async (values: Record<string, JsonValue>) => {
const onCreate = async (initialValues: Record<string, JsonValue>) => {
if (isCreating) {
return;
}
setIsCreating(true);
const { formState: values, secrets } = await decorators.run({
formState: initialValues,
secrets: contextSecrets,
});
const { taskId } = await scaffolderApi.scaffold({
templateRef,
values,
@@ -113,6 +132,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
>
<TemplateWizardPageContextMenu editUrl={editUrl} />
</Header>
{isCreating && <Progress />}
<Workflow
namespace={namespace}
templateName={templateName}
@@ -0,0 +1,16 @@
/*
* Copyright 2024 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 './useFormDecorators';
@@ -19,13 +19,20 @@ import useAsync from 'react-use/esm/useAsync';
import { useMemo } from 'react';
import { ScaffolderFormDecoratorContext } from '@backstage/plugin-scaffolder-react/alpha';
import { OpaqueFormDecorator } from '@internal/scaffolder';
import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react';
import { JsonValue } from '@backstage/types';
/** @internal */
type BoundFieldDecorator = {
decorator: (ctx: ScaffolderFormDecoratorContext) => Promise<void>;
};
export const useFormDecorators = () => {
/** @alpha */
export const useFormDecorators = ({
manifest,
}: {
manifest?: TemplateParameterSchema;
}) => {
const formDecoratorsApi = useApi(formDecoratorsApiRef);
const errorApi = useApi(errorApiRef);
const { value: decorators } = useAsync(
@@ -34,7 +41,7 @@ export const useFormDecorators = () => {
);
const apiHolder = useApiHolder();
return useMemo(() => {
const boundDecorators = useMemo(() => {
const decoratorsMap = new Map<string, BoundFieldDecorator>();
for (const decorator of decorators ?? []) {
@@ -62,4 +69,50 @@ export const useFormDecorators = () => {
}
return decoratorsMap;
}, [apiHolder, decorators, errorApi]);
return {
run: async (opts: {
formState: Record<string, JsonValue>;
secrets: Record<string, string>;
}) => {
let formState: Record<string, JsonValue> = { ...opts.formState };
let secrets: Record<string, string> = {};
if (manifest?.EXPERIMENTAL_formDecorators) {
// for each of the form decorators, go and call the decorator with the context
await Promise.all(
manifest.EXPERIMENTAL_formDecorators.map(async decorator => {
const formDecorator = boundDecorators?.get(decorator.id);
if (!formDecorator) {
errorApi.post(
new Error(`Failed to find form decorator ${decorator.id}`),
);
return;
}
await formDecorator.decorator({
setSecrets: (
handler: (
oldState: Record<string, string>,
) => Record<string, string>,
) => {
secrets = { ...handler(secrets) };
},
setFormState: (
handler: (
oldState: Record<string, JsonValue>,
) => Record<string, JsonValue>,
) => {
formState = { ...handler(formState) };
},
formState,
input: decorator.input ?? {},
});
}),
);
}
return { formState, secrets };
},
};
};
+2
View File
@@ -22,6 +22,8 @@ export {
type ScaffolderTemplateFormPreviewerClassKey,
} from './components';
export * from './hooks';
export { scaffolderTranslationRef } from '../translation';
export * from './api';