feat(scaffolder): added some simple extractors to grab the data

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2021-05-19 11:41:27 +02:00
parent bcead6b799
commit ca185ff577
7 changed files with 123 additions and 22 deletions
+5 -2
View File
@@ -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
+11 -14
View File
@@ -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;
};
+20 -1
View File
@@ -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;
},
};
}
+1
View File
@@ -20,6 +20,7 @@ export {
ScaffolderPage,
OwnerPickerFieldExtension,
RepoUrlPickerFieldExtension,
ScaffolderCustomFields,
} from './plugin';
export type { ScaffolderApi } from './api';
export { ScaffolderClient, scaffolderApiRef } from './api';
+9 -1
View File
@@ -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(),
);