feat: more work on form decorators

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-10-28 16:22:34 +01:00
parent 8d732a8d75
commit 7e3ca8be71
13 changed files with 109 additions and 68 deletions
+12
View File
@@ -26,6 +26,9 @@ import {
discoveryApiRef,
} from '@backstage/core-plugin-api';
import { AuthProxyDiscoveryApi } from './AuthProxyDiscoveryApi';
import { formDecoratorsApiRef } from '@backstage/plugin-scaffolder/alpha';
import { DefaultScaffolderFormDecoratorsApi } from '@backstage/plugin-scaffolder/alpha';
import { mockDecorator } from './components/scaffolder/decorators';
export const apis: AnyApiFactory[] = [
createApiFactory({
@@ -39,5 +42,14 @@ export const apis: AnyApiFactory[] = [
factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
}),
createApiFactory({
api: formDecoratorsApiRef,
deps: {},
factory: () =>
DefaultScaffolderFormDecoratorsApi.create({
decorators: [mockDecorator],
}),
}),
ScmAuth.createDefaultApiFactory(),
];
@@ -0,0 +1,33 @@
/*
* 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.
*/
import { githubAuthApiRef } from '@backstage/core-plugin-api';
import { createScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
export const mockDecorator = createScaffolderFormDecorator({
id: 'githubOauth',
schema: {
input: {
test: z => z.string(),
},
},
deps: {
githubApi: githubAuthApiRef,
},
fn: async ({ setSecrets }, { githubApi }) => {
const token = await githubApi.getAccessToken();
setSecrets(state => ({ ...state, GITHUB_TOKEN: token }));
},
});
@@ -174,16 +174,19 @@
}
},
"EXPERIMENTAL_formDecorators": {
"type": "object",
"type": "array",
"description": "A list of decorators and their inputs that the form should trigger before submitting the job",
"properties": {
"id": {
"type": "string",
"description": "The form hook ID"
},
"input": {
"type": "object",
"description": "A object describing the inputs to the form hook."
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The form hook ID"
},
"input": {
"type": "object",
"description": "A object describing the inputs to the form hook."
}
}
}
},
@@ -95,11 +95,11 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
async (originalFormState: Record<string, JsonValue>) => {
let formState: Record<string, JsonValue> = { ...originalFormState };
if (manifest?.EXPERIMENTAL_formDecorators && formDecorators?.size) {
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);
const formDecorator = formDecorators?.get(decorator.id);
if (!formDecorator) {
// eslint-disable-next-line no-console
console.error('Failed to find form decorator', decorator.id);
@@ -52,6 +52,12 @@ export type ScaffolderFormDecorator<
) => Promise<void>;
};
/** @alpha */
export type AnyScaffolderFormDecorator = ScaffolderFormDecorator<
{ [key in string]: (zImpl: typeof z) => z.ZodType },
{ [key in string]: AnyApiRef },
any
>;
/**
* Method for creating decorators which can be used to collect
* secrets from the user before submitting to the backend.
-46
View File
@@ -1,46 +0,0 @@
/*
* 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.
*/
import {
ApiBlueprint,
createApiFactory,
discoveryApiRef,
fetchApiRef,
identityApiRef,
} from '@backstage/frontend-plugin-api';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
import { ScaffolderClient } from '../api';
export const scaffolderApi = ApiBlueprint.make({
params: {
factory: createApiFactory({
api: scaffolderApiRef,
deps: {
discoveryApi: discoveryApiRef,
scmIntegrationsApi: scmIntegrationsApiRef,
fetchApi: fetchApiRef,
identityApi: identityApiRef,
},
factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) =>
new ScaffolderClient({
discoveryApi,
scmIntegrationsApi,
fetchApi,
identityApi,
}),
}),
},
});
@@ -15,24 +15,25 @@
*/
import { ScaffolderFormDecoratorsApi } from './types';
import { ScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
import { AnyScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
/** @alpha */
export class DefaultScaffolderFormDecoratorsApi
implements ScaffolderFormDecoratorsApi
{
private constructor(
private readonly options: {
decorators: Array<ScaffolderFormDecorator>;
decorators: Array<AnyScaffolderFormDecorator>;
},
) {}
static create(options?: { decorators: ScaffolderFormDecorator[] }) {
static create(options?: { decorators: AnyScaffolderFormDecorator[] }) {
return new DefaultScaffolderFormDecoratorsApi(
options ?? { decorators: [] },
);
}
async getFormDecorators(): Promise<ScaffolderFormDecorator[]> {
async getFormDecorators(): Promise<AnyScaffolderFormDecorator[]> {
return this.options.decorators;
}
}
+6 -2
View File
@@ -14,5 +14,9 @@
* limitations under the License.
*/
export { formFieldsApiRef } from './ref';
export type { ScaffolderFormFieldsApi } from './types';
export { formFieldsApiRef, formDecoratorsApiRef } from './ref';
export type {
ScaffolderFormFieldsApi,
ScaffolderFormDecoratorsApi,
} from './types';
export { DefaultScaffolderFormDecoratorsApi } from './FormDecoratorsApi';
+2 -2
View File
@@ -16,7 +16,7 @@
import {
FormFieldExtensionData,
ScaffolderFormDecorator,
AnyScaffolderFormDecorator,
} from '@backstage/plugin-scaffolder-react/alpha';
export interface ScaffolderFormFieldsApi {
@@ -24,5 +24,5 @@ export interface ScaffolderFormFieldsApi {
}
export interface ScaffolderFormDecoratorsApi {
getFormDecorators(): Promise<ScaffolderFormDecorator[]>;
getFormDecorators(): Promise<AnyScaffolderFormDecorator[]>;
}
@@ -21,11 +21,19 @@ import {
import {
NavItemBlueprint,
PageBlueprint,
ApiBlueprint,
createApiFactory,
discoveryApiRef,
fetchApiRef,
identityApiRef,
} from '@backstage/frontend-plugin-api';
import React from 'react';
import { rootRouteRef } from '../routes';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import { FormFieldBlueprint } from '@backstage/plugin-scaffolder-react/alpha';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
import { ScaffolderClient } from '../api';
export const scaffolderPage = PageBlueprint.make({
params: {
@@ -50,3 +58,24 @@ export const repoUrlPickerFormField = FormFieldBlueprint.make({
field: () => import('./fields/RepoUrlPicker').then(m => m.RepoUrlPicker),
},
});
export const scaffolderApi = ApiBlueprint.make({
params: {
factory: createApiFactory({
api: scaffolderApiRef,
deps: {
discoveryApi: discoveryApiRef,
scmIntegrationsApi: scmIntegrationsApiRef,
fetchApi: fetchApiRef,
identityApi: identityApiRef,
},
factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) =>
new ScaffolderClient({
discoveryApi,
scmIntegrationsApi,
fetchApi,
identityApi,
}),
}),
},
});
@@ -52,7 +52,6 @@ describe('useFormDecorators', () => {
[
formDecoratorsApiRef,
DefaultScaffolderFormDecoratorsApi.create({
// @ts-expect-error - todo
decorators: [mockDecorator],
}),
],
@@ -90,7 +89,6 @@ describe('useFormDecorators', () => {
[
formDecoratorsApiRef,
DefaultScaffolderFormDecoratorsApi.create({
// @ts-expect-error - todo
decorators: [mockDecorator],
}),
],
+1
View File
@@ -23,5 +23,6 @@ export {
} from './components';
export { scaffolderTranslationRef } from '../translation';
export * from './api';
export { default } from './plugin';
+1 -1
View File
@@ -26,11 +26,11 @@ import {
selectedTemplateRouteRef,
viewTechDocRouteRef,
} from '../routes';
import { scaffolderApi } from './api';
import {
repoUrlPickerFormField,
scaffolderNavItem,
scaffolderPage,
scaffolderApi,
} from './extensions';
import { formFieldsApi } from './api/FormFieldsApi';