chore: refactor, and move the execution
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -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 };
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ export {
|
||||
type ScaffolderTemplateFormPreviewerClassKey,
|
||||
} from './components';
|
||||
|
||||
export * from './hooks';
|
||||
|
||||
export { scaffolderTranslationRef } from '../translation';
|
||||
export * from './api';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user