feat(scaffolder): added some simple extractors to grab the data
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -47,6 +47,7 @@ import {
|
||||
scaffolderPlugin,
|
||||
OwnerPickerFieldExtension,
|
||||
RepoUrlPickerFieldExtension,
|
||||
ScaffolderCustomFields,
|
||||
} from '@backstage/plugin-scaffolder';
|
||||
import { SearchPage, SearchPageNext } from '@backstage/plugin-search';
|
||||
import { TechRadarPage } from '@backstage/plugin-tech-radar';
|
||||
@@ -113,8 +114,10 @@ const routes = (
|
||||
<Route path="/catalog-import" element={<CatalogImportPage />} />
|
||||
<Route path="/docs" element={<TechdocsPage />} />
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<RepoUrlPickerFieldExtension />
|
||||
<OwnerPickerFieldExtension />
|
||||
<ScaffolderCustomFields>
|
||||
<RepoUrlPickerFieldExtension />
|
||||
<OwnerPickerFieldExtension />
|
||||
</ScaffolderCustomFields>
|
||||
</Route>
|
||||
<Route path="/explore" element={<ExplorePage />} />
|
||||
<Route
|
||||
|
||||
@@ -20,23 +20,20 @@ import { ScaffolderPage } from './ScaffolderPage';
|
||||
import { TemplatePage } from './TemplatePage';
|
||||
import { TaskPage } from './TaskPage';
|
||||
import { ActionsPage } from './ActionsPage';
|
||||
import { getComponentData } from '@backstage/core';
|
||||
import { FieldExtensionOptions } from '../extensions';
|
||||
import {
|
||||
FieldExtensionOptions,
|
||||
FIELD_EXTENSION_WRAPPER_KEY,
|
||||
FIELD_EXTENSION_KEY,
|
||||
} from '../extensions';
|
||||
import { collect, collectChildren } from '../extensions/helpers';
|
||||
|
||||
export const Router = () => {
|
||||
const children = useOutlet();
|
||||
const fieldExtensions = React.Children.map(children ?? [], child => {
|
||||
if (!React.isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
const outlet = useOutlet();
|
||||
|
||||
const data = getComponentData<FieldExtensionOptions<unknown>>(
|
||||
child,
|
||||
'scaffolder.extensions.field.v1',
|
||||
);
|
||||
|
||||
return data;
|
||||
}).filter(Boolean);
|
||||
const fieldExtensions = collect<FieldExtensionOptions>(
|
||||
collectChildren(outlet, FIELD_EXTENSION_WRAPPER_KEY).flat(),
|
||||
FIELD_EXTENSION_KEY,
|
||||
);
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
|
||||
@@ -56,9 +56,9 @@ function isObject(obj: unknown): obj is JsonObject {
|
||||
|
||||
export const createValidator = (
|
||||
rootSchema: JsonObject,
|
||||
validators: Map<
|
||||
validators: Record<
|
||||
string,
|
||||
(value: JsonValue, validation: FieldValidation) => void
|
||||
undefined | ((value: JsonValue, validation: FieldValidation) => void)
|
||||
>,
|
||||
) => {
|
||||
function validate(
|
||||
@@ -87,8 +87,8 @@ export const createValidator = (
|
||||
const propSchema = schemaProps[key];
|
||||
const fieldName =
|
||||
isObject(propSchema) && (propSchema['ui:field'] as string);
|
||||
if (fieldName && validators.has(fieldName)) {
|
||||
validators.get(fieldName)!(propData as JsonValue, propValidation);
|
||||
if (fieldName && typeof validators[fieldName] === 'function') {
|
||||
validators[fieldName]!(propData as JsonValue, propValidation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 React from 'react';
|
||||
import { getComponentData } from '@backstage/core';
|
||||
|
||||
export const collect = <T>(
|
||||
children: React.ReactNode,
|
||||
componentDataKey: string,
|
||||
) => {
|
||||
const stack = [children];
|
||||
const found: T[] = [];
|
||||
|
||||
while (stack.length) {
|
||||
const current: React.ReactNode = stack.pop()!;
|
||||
|
||||
React.Children.forEach(current, child => {
|
||||
if (!React.isValidElement(child)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = getComponentData<T>(child, componentDataKey);
|
||||
if (data) {
|
||||
found.push(data);
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
stack.push(child.props.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return found;
|
||||
};
|
||||
|
||||
export const collectChildren = (
|
||||
component: React.ReactNode,
|
||||
componentDataKey: string,
|
||||
) => {
|
||||
const stack = [component];
|
||||
const found: React.ReactNode[] = [];
|
||||
|
||||
while (stack.length) {
|
||||
const current: React.ReactNode = stack.pop()!;
|
||||
|
||||
React.Children.forEach(current, child => {
|
||||
if (!React.isValidElement(child)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
if (getComponentData(child, componentDataKey)) {
|
||||
found.push(child.props.children);
|
||||
}
|
||||
stack.push(child.props.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return found;
|
||||
};
|
||||
@@ -22,6 +22,9 @@ export type FieldExtensionOptions<T = any> = {
|
||||
validation?: (data: T, field: FieldValidation) => void;
|
||||
};
|
||||
|
||||
export const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1';
|
||||
export const FIELD_EXTENSION_KEY = 'scaffolder.extensions.field.v1';
|
||||
|
||||
export function createScaffolderFieldExtension<T = any>(
|
||||
options: FieldExtensionOptions<T>,
|
||||
): Extension<() => null> {
|
||||
@@ -31,7 +34,7 @@ export function createScaffolderFieldExtension<T = any>(
|
||||
|
||||
attachComponentData(
|
||||
FieldExtensionDataHolder,
|
||||
'scaffolder.extensions.field.v1',
|
||||
FIELD_EXTENSION_KEY,
|
||||
options,
|
||||
);
|
||||
|
||||
@@ -39,3 +42,19 @@ export function createScaffolderFieldExtension<T = any>(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createScaffolderFieldExtensionWrapper(): Extension<() => null> {
|
||||
return {
|
||||
expose() {
|
||||
const FieldExtensionWrapperDataHolder: any = () => null;
|
||||
|
||||
attachComponentData(
|
||||
FieldExtensionWrapperDataHolder,
|
||||
FIELD_EXTENSION_WRAPPER_KEY,
|
||||
true,
|
||||
);
|
||||
|
||||
return FieldExtensionWrapperDataHolder;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
ScaffolderPage,
|
||||
OwnerPickerFieldExtension,
|
||||
RepoUrlPickerFieldExtension,
|
||||
ScaffolderCustomFields,
|
||||
} from './plugin';
|
||||
export type { ScaffolderApi } from './api';
|
||||
export { ScaffolderClient, scaffolderApiRef } from './api';
|
||||
|
||||
@@ -26,7 +26,10 @@ import { OwnerPicker } from './components/fields/OwnerPicker';
|
||||
import { RepoUrlPicker } from './components/fields/RepoUrlPicker';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { scaffolderApiRef, ScaffolderClient } from './api';
|
||||
import { createScaffolderFieldExtension } from './extensions';
|
||||
import {
|
||||
createScaffolderFieldExtension,
|
||||
createScaffolderFieldExtensionWrapper,
|
||||
} from './extensions';
|
||||
import { rootRouteRef, registerComponentRouteRef } from './routes';
|
||||
|
||||
export const scaffolderPlugin = createPlugin({
|
||||
@@ -76,9 +79,14 @@ export const OwnerPickerFieldExtension = scaffolderPlugin.provide(
|
||||
name: 'OwnerPicker',
|
||||
}),
|
||||
);
|
||||
|
||||
export const ScaffolderPage = scaffolderPlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () => import('./components/Router').then(m => m.Router),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const ScaffolderCustomFields = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtensionWrapper(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user