feat(scaffolder): implement custom field explorer
Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': minor
|
||||
---
|
||||
|
||||
Implement Custom Field Explorer to view and play around with available installed custom field extensions
|
||||
@@ -35,6 +35,7 @@ import { TaskStep } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { UIOptionsType } from '@rjsf/utils';
|
||||
import { UiSchema } from '@rjsf/utils';
|
||||
import { z } from 'zod';
|
||||
|
||||
// @alpha
|
||||
export function createNextScaffolderFieldExtension<
|
||||
@@ -57,6 +58,12 @@ export function createScaffolderLayout<TInputProps = unknown>(
|
||||
options: LayoutOptions,
|
||||
): Extension<LayoutComponent<TInputProps>>;
|
||||
|
||||
// @public
|
||||
export type CustomFieldExtensionSchema = {
|
||||
uiOptions?: JSONSchema7;
|
||||
returnValue?: JSONSchema7;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CustomFieldValidator<TFieldReturnValue> = (
|
||||
data: TFieldReturnValue,
|
||||
@@ -75,36 +82,78 @@ export const EntityNamePickerFieldExtension: FieldExtensionComponent<
|
||||
// @public
|
||||
export const EntityPickerFieldExtension: FieldExtensionComponent<
|
||||
string,
|
||||
EntityPickerUiOptions
|
||||
{
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface EntityPickerUiOptions {
|
||||
// (undocumented)
|
||||
allowArbitraryValues?: boolean;
|
||||
// (undocumented)
|
||||
allowedKinds?: string[];
|
||||
// (undocumented)
|
||||
defaultKind?: string;
|
||||
// (undocumented)
|
||||
defaultNamespace?: string | false;
|
||||
}
|
||||
export type EntityPickerUiOptions = z.infer<typeof EntityPickerUiOptionsSchema>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityPickerUiOptionsSchema: z.ZodObject<
|
||||
{
|
||||
allowedKinds: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
defaultKind: z.ZodOptional<z.ZodString>;
|
||||
allowArbitraryValues: z.ZodOptional<z.ZodBoolean>;
|
||||
defaultNamespace: z.ZodOptional<
|
||||
z.ZodUnion<[z.ZodString, z.ZodLiteral<false>]>
|
||||
>;
|
||||
},
|
||||
'strip',
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
},
|
||||
{
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const EntityTagsPickerFieldExtension: FieldExtensionComponent<
|
||||
string[],
|
||||
EntityTagsPickerUiOptions
|
||||
{
|
||||
showCounts?: boolean | undefined;
|
||||
kinds?: string[] | undefined;
|
||||
helperText?: string | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface EntityTagsPickerUiOptions {
|
||||
// (undocumented)
|
||||
helperText?: string;
|
||||
// (undocumented)
|
||||
kinds?: string[];
|
||||
// (undocumented)
|
||||
showCounts?: boolean;
|
||||
}
|
||||
export type EntityTagsPickerUiOptions = z.infer<
|
||||
typeof EntityTagsPickerUiOptionsSchema
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityTagsPickerUiOptionsSchema: z.ZodObject<
|
||||
{
|
||||
kinds: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
showCounts: z.ZodOptional<z.ZodBoolean>;
|
||||
helperText: z.ZodOptional<z.ZodString>;
|
||||
},
|
||||
'strip',
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
showCounts?: boolean | undefined;
|
||||
kinds?: string[] | undefined;
|
||||
helperText?: string | undefined;
|
||||
},
|
||||
{
|
||||
showCounts?: boolean | undefined;
|
||||
kinds?: string[] | undefined;
|
||||
helperText?: string | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export type FieldExtensionComponent<_TReturnValue, _TInputProps> = () => null;
|
||||
@@ -130,6 +179,7 @@ export type FieldExtensionOptions<
|
||||
props: FieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
|
||||
) => JSX.Element | null;
|
||||
validation?: CustomFieldValidator<TFieldReturnValue>;
|
||||
schema?: CustomFieldExtensionSchema;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -199,6 +249,7 @@ export type NextFieldExtensionOptions<
|
||||
props: NextFieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
|
||||
) => JSX.Element | null;
|
||||
validation?: NextCustomFieldValidator<TFieldReturnValue>;
|
||||
schema?: CustomFieldExtensionSchema;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
@@ -228,36 +279,80 @@ export const nextSelectedTemplateRouteRef: SubRouteRef<
|
||||
// @public
|
||||
export const OwnedEntityPickerFieldExtension: FieldExtensionComponent<
|
||||
string,
|
||||
OwnedEntityPickerUiOptions
|
||||
{
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface OwnedEntityPickerUiOptions {
|
||||
// (undocumented)
|
||||
allowArbitraryValues?: boolean;
|
||||
// (undocumented)
|
||||
allowedKinds?: string[];
|
||||
// (undocumented)
|
||||
defaultKind?: string;
|
||||
// (undocumented)
|
||||
defaultNamespace?: string | false;
|
||||
}
|
||||
export type OwnedEntityPickerUiOptions = z.infer<
|
||||
typeof OwnedEntityPickerUiOptionsSchema
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const OwnedEntityPickerUiOptionsSchema: z.ZodObject<
|
||||
{
|
||||
allowedKinds: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
defaultKind: z.ZodOptional<z.ZodString>;
|
||||
allowArbitraryValues: z.ZodOptional<z.ZodBoolean>;
|
||||
defaultNamespace: z.ZodOptional<
|
||||
z.ZodUnion<[z.ZodString, z.ZodLiteral<false>]>
|
||||
>;
|
||||
},
|
||||
'strip',
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
},
|
||||
{
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const OwnerPickerFieldExtension: FieldExtensionComponent<
|
||||
string,
|
||||
OwnerPickerUiOptions
|
||||
{
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface OwnerPickerUiOptions {
|
||||
// (undocumented)
|
||||
allowArbitraryValues?: boolean;
|
||||
// (undocumented)
|
||||
allowedKinds?: string[];
|
||||
// (undocumented)
|
||||
defaultNamespace?: string | false;
|
||||
}
|
||||
export type OwnerPickerUiOptions = z.infer<typeof OwnerPickerUiOptionsSchema>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const OwnerPickerUiOptionsSchema: z.ZodObject<
|
||||
{
|
||||
allowedKinds: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString, 'many'>>>;
|
||||
allowArbitraryValues: z.ZodOptional<z.ZodBoolean>;
|
||||
defaultNamespace: z.ZodOptional<
|
||||
z.ZodUnion<[z.ZodString, z.ZodLiteral<false>]>
|
||||
>;
|
||||
},
|
||||
'strip',
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
},
|
||||
{
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const repoPickerValidation: (
|
||||
@@ -271,31 +366,144 @@ export const repoPickerValidation: (
|
||||
// @public
|
||||
export const RepoUrlPickerFieldExtension: FieldExtensionComponent<
|
||||
string,
|
||||
RepoUrlPickerUiOptions
|
||||
{
|
||||
allowedOwners?: string[] | undefined;
|
||||
allowedOrganizations?: string[] | undefined;
|
||||
allowedRepos?: string[] | undefined;
|
||||
allowedHosts?: string[] | undefined;
|
||||
requestUserCredentials?:
|
||||
| {
|
||||
additionalScopes?:
|
||||
| {
|
||||
azure?: string[] | undefined;
|
||||
github?: string[] | undefined;
|
||||
gitlab?: string[] | undefined;
|
||||
bitbucket?: string[] | undefined;
|
||||
gerrit?: string[] | undefined;
|
||||
}
|
||||
| undefined;
|
||||
secretsKey: string;
|
||||
}
|
||||
| undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface RepoUrlPickerUiOptions {
|
||||
// (undocumented)
|
||||
allowedHosts?: string[];
|
||||
// (undocumented)
|
||||
allowedOrganizations?: string[];
|
||||
// (undocumented)
|
||||
allowedOwners?: string[];
|
||||
// (undocumented)
|
||||
allowedRepos?: string[];
|
||||
// (undocumented)
|
||||
requestUserCredentials?: {
|
||||
secretsKey: string;
|
||||
additionalScopes?: {
|
||||
gerrit?: string[];
|
||||
github?: string[];
|
||||
gitlab?: string[];
|
||||
bitbucket?: string[];
|
||||
azure?: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
export type RepoUrlPickerUiOptions = z.infer<
|
||||
typeof RepoUrlPickerUiOptionsSchema
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const RepoUrlPickerUiOptionsSchema: z.ZodObject<
|
||||
{
|
||||
allowedHosts: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
allowedOrganizations: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
allowedOwners: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
allowedRepos: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
requestUserCredentials: z.ZodOptional<
|
||||
z.ZodObject<
|
||||
{
|
||||
secretsKey: z.ZodString;
|
||||
additionalScopes: z.ZodOptional<
|
||||
z.ZodObject<
|
||||
{
|
||||
gerrit: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
github: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
gitlab: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
bitbucket: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
azure: z.ZodOptional<z.ZodArray<z.ZodString, 'many'>>;
|
||||
},
|
||||
'strip',
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
azure?: string[] | undefined;
|
||||
github?: string[] | undefined;
|
||||
gitlab?: string[] | undefined;
|
||||
bitbucket?: string[] | undefined;
|
||||
gerrit?: string[] | undefined;
|
||||
},
|
||||
{
|
||||
azure?: string[] | undefined;
|
||||
github?: string[] | undefined;
|
||||
gitlab?: string[] | undefined;
|
||||
bitbucket?: string[] | undefined;
|
||||
gerrit?: string[] | undefined;
|
||||
}
|
||||
>
|
||||
>;
|
||||
},
|
||||
'strip',
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
additionalScopes?:
|
||||
| {
|
||||
azure?: string[] | undefined;
|
||||
github?: string[] | undefined;
|
||||
gitlab?: string[] | undefined;
|
||||
bitbucket?: string[] | undefined;
|
||||
gerrit?: string[] | undefined;
|
||||
}
|
||||
| undefined;
|
||||
secretsKey: string;
|
||||
},
|
||||
{
|
||||
additionalScopes?:
|
||||
| {
|
||||
azure?: string[] | undefined;
|
||||
github?: string[] | undefined;
|
||||
gitlab?: string[] | undefined;
|
||||
bitbucket?: string[] | undefined;
|
||||
gerrit?: string[] | undefined;
|
||||
}
|
||||
| undefined;
|
||||
secretsKey: string;
|
||||
}
|
||||
>
|
||||
>;
|
||||
},
|
||||
'strip',
|
||||
z.ZodTypeAny,
|
||||
{
|
||||
allowedOwners?: string[] | undefined;
|
||||
allowedOrganizations?: string[] | undefined;
|
||||
allowedRepos?: string[] | undefined;
|
||||
allowedHosts?: string[] | undefined;
|
||||
requestUserCredentials?:
|
||||
| {
|
||||
additionalScopes?:
|
||||
| {
|
||||
azure?: string[] | undefined;
|
||||
github?: string[] | undefined;
|
||||
gitlab?: string[] | undefined;
|
||||
bitbucket?: string[] | undefined;
|
||||
gerrit?: string[] | undefined;
|
||||
}
|
||||
| undefined;
|
||||
secretsKey: string;
|
||||
}
|
||||
| undefined;
|
||||
},
|
||||
{
|
||||
allowedOwners?: string[] | undefined;
|
||||
allowedOrganizations?: string[] | undefined;
|
||||
allowedRepos?: string[] | undefined;
|
||||
allowedHosts?: string[] | undefined;
|
||||
requestUserCredentials?:
|
||||
| {
|
||||
additionalScopes?:
|
||||
| {
|
||||
azure?: string[] | undefined;
|
||||
github?: string[] | undefined;
|
||||
gitlab?: string[] | undefined;
|
||||
bitbucket?: string[] | undefined;
|
||||
gerrit?: string[] | undefined;
|
||||
}
|
||||
| undefined;
|
||||
secretsKey: string;
|
||||
}
|
||||
| undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const rootRouteRef: RouteRef<undefined>;
|
||||
|
||||
@@ -74,7 +74,9 @@
|
||||
"react-use": "^17.2.4",
|
||||
"use-immer": "^0.7.0",
|
||||
"yaml": "^2.0.0",
|
||||
"zen-observable": "^0.8.15"
|
||||
"zen-observable": "^0.8.15",
|
||||
"zod": "^3.11.6",
|
||||
"zod-to-json-schema": "^3.18.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2022 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 { StreamLanguage } from '@codemirror/language';
|
||||
import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
FormControl,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
Select,
|
||||
} from '@material-ui/core';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import { withTheme } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import yaml from 'yaml';
|
||||
import { FieldExtensionOptions } from '../../extensions';
|
||||
import * as fieldOverrides from '../MultistepJsonForm/FieldOverrides';
|
||||
import { TemplateEditorForm } from './TemplateEditorForm';
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
gridArea: 'pageContent',
|
||||
display: 'grid',
|
||||
gridTemplateAreas: `
|
||||
"controls controls"
|
||||
"fieldForm preview"
|
||||
`,
|
||||
gridTemplateRows: 'auto 1fr',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
},
|
||||
controls: {
|
||||
gridArea: 'controls',
|
||||
display: 'flex',
|
||||
flexFlow: 'row nowrap',
|
||||
alignItems: 'center',
|
||||
margin: theme.spacing(1),
|
||||
},
|
||||
fieldForm: {
|
||||
gridArea: 'fieldForm',
|
||||
},
|
||||
preview: {
|
||||
gridArea: 'preview',
|
||||
},
|
||||
}));
|
||||
|
||||
export const CustomFieldExplorer = ({
|
||||
customFieldExtensions = [],
|
||||
onClose,
|
||||
}: {
|
||||
customFieldExtensions?: FieldExtensionOptions<any, any>[];
|
||||
onClose?: () => void;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const fieldOptions = customFieldExtensions.filter(field => !!field.schema);
|
||||
const [selectedField, setSelectedField] = useState(fieldOptions[0]);
|
||||
const [fieldFormState, setFieldFormState] = useState({});
|
||||
const [formState, setFormState] = useState({});
|
||||
const [refreshKey, setRefreshKey] = useState(Date.now());
|
||||
const sampleFieldTemplate = useMemo(
|
||||
() =>
|
||||
yaml.stringify({
|
||||
parameters: [
|
||||
{
|
||||
title: `${selectedField.name} Example`,
|
||||
properties: {
|
||||
[selectedField.name]: {
|
||||
type: selectedField.schema?.returnValue?.type,
|
||||
'ui:field': selectedField.name,
|
||||
'ui:options': fieldFormState,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
[fieldFormState, selectedField],
|
||||
);
|
||||
|
||||
const fieldComponents = useMemo(() => {
|
||||
return Object.fromEntries(
|
||||
customFieldExtensions.map(({ name, component }) => [name, component]),
|
||||
);
|
||||
}, [customFieldExtensions]);
|
||||
|
||||
const handleSelectionChange = useCallback(
|
||||
selection => {
|
||||
setSelectedField(selection);
|
||||
setFieldFormState({});
|
||||
setFormState({});
|
||||
},
|
||||
[setFieldFormState, setFormState, setSelectedField],
|
||||
);
|
||||
|
||||
const handleFieldConfigChange = useCallback(
|
||||
state => {
|
||||
setFieldFormState(state);
|
||||
setFormState({});
|
||||
// Force TemplateEditorForm to re-render since some fields
|
||||
// may not be responsive to ui:option changes
|
||||
setRefreshKey(Date.now());
|
||||
},
|
||||
[setFieldFormState, setRefreshKey],
|
||||
);
|
||||
|
||||
return (
|
||||
<main className={classes.root}>
|
||||
<div className={classes.controls}>
|
||||
<FormControl variant="outlined" size="small" fullWidth>
|
||||
<InputLabel id="select-field-label">
|
||||
Choose Custom Field Extension
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={selectedField}
|
||||
label="Choose Custom Field Extension"
|
||||
labelId="select-field-label"
|
||||
onChange={e => handleSelectionChange(e.target.value)}
|
||||
>
|
||||
{fieldOptions.map((option, idx) => (
|
||||
<MenuItem key={idx} value={option as any}>
|
||||
{option.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<IconButton size="medium" onClick={onClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className={classes.fieldForm}>
|
||||
<Card>
|
||||
<CardHeader title="Field Options" />
|
||||
<CardContent>
|
||||
<Form
|
||||
showErrorList={false}
|
||||
fields={{ ...fieldOverrides, ...fieldComponents }}
|
||||
noHtml5Validate
|
||||
formData={fieldFormState}
|
||||
formContext={{ fieldFormState }}
|
||||
onSubmit={e => handleFieldConfigChange(e.formData)}
|
||||
schema={selectedField.schema?.uiOptions || {}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={!selectedField.schema?.uiOptions}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className={classes.preview}>
|
||||
<Card>
|
||||
<CardHeader title="Example Template Spec" />
|
||||
<CardContent>
|
||||
<CodeMirror
|
||||
readOnly
|
||||
theme="dark"
|
||||
height="100%"
|
||||
extensions={[StreamLanguage.define(yamlSupport)]}
|
||||
value={sampleFieldTemplate}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TemplateEditorForm
|
||||
key={refreshKey}
|
||||
content={sampleFieldTemplate}
|
||||
contentIsSpec
|
||||
fieldExtensions={customFieldExtensions}
|
||||
data={formState}
|
||||
onUpdate={setFormState}
|
||||
setErrorText={() => null}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@@ -44,7 +44,7 @@ const useStyles = makeStyles(theme => ({
|
||||
|
||||
interface EditorIntroProps {
|
||||
style?: JSX.IntrinsicElements['div']['style'];
|
||||
onSelect?: (option: 'local' | 'form') => void;
|
||||
onSelect?: (option: 'local' | 'form' | 'field-explorer') => void;
|
||||
}
|
||||
|
||||
export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
@@ -104,6 +104,22 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
</Card>
|
||||
);
|
||||
|
||||
const cardFieldExplorer = (
|
||||
<Card className={classes.card} elevation={4}>
|
||||
<CardActionArea onClick={() => props.onSelect?.('field-explorer')}>
|
||||
<CardContent>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Custom Field Explorer
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
View and play around with available installed custom field
|
||||
extensions.
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={props.style}>
|
||||
<Typography variant="h6" className={classes.introText}>
|
||||
@@ -121,6 +137,7 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
{supportsLoad && cardLoadLocal}
|
||||
{cardFormEditor}
|
||||
{!supportsLoad && cardLoadLocal}
|
||||
{cardFieldExplorer}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
TemplateDirectoryAccess,
|
||||
WebFileSystemAccess,
|
||||
} from '../../lib/filesystem';
|
||||
import { CustomFieldExplorer } from './CustomFieldExplorer';
|
||||
import { TemplateEditorIntro } from './TemplateEditorIntro';
|
||||
import { TemplateEditor } from './TemplateEditor';
|
||||
import { TemplateFormPreviewer } from './TemplateFormPreviewer';
|
||||
@@ -32,6 +33,9 @@ type Selection =
|
||||
}
|
||||
| {
|
||||
type: 'form';
|
||||
}
|
||||
| {
|
||||
type: 'field-explorer';
|
||||
};
|
||||
|
||||
interface TemplateEditorPageProps {
|
||||
@@ -62,6 +66,13 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) {
|
||||
layouts={props.layouts}
|
||||
/>
|
||||
);
|
||||
} else if (selection?.type === 'field-explorer') {
|
||||
content = (
|
||||
<CustomFieldExplorer
|
||||
customFieldExtensions={props.customFieldExtensions}
|
||||
onClose={() => setSelection(undefined)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Content>
|
||||
@@ -73,6 +84,8 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) {
|
||||
.catch(() => {});
|
||||
} else if (option === 'form') {
|
||||
setSelection({ type: 'form' });
|
||||
} else if (option === 'field-explorer') {
|
||||
setSelection({ type: 'field-explorer' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -15,13 +15,16 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import { EntityNamePickerReturnValue } from './schema';
|
||||
import { TextField } from '@material-ui/core';
|
||||
|
||||
export { EntityNamePickerSchema } from './schema';
|
||||
|
||||
/**
|
||||
* EntityName Picker
|
||||
*/
|
||||
export const EntityNamePicker = (
|
||||
props: FieldExtensionComponentProps<string>,
|
||||
props: FieldExtensionComponentProps<EntityNamePickerReturnValue>,
|
||||
) => {
|
||||
const {
|
||||
onChange,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2022 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 { JSONSchema7 } from 'json-schema';
|
||||
import { z } from 'zod';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
const EntityNamePickerReturnValueSchema = z.string();
|
||||
|
||||
export type EntityNamePickerReturnValue = z.infer<
|
||||
typeof EntityNamePickerReturnValueSchema
|
||||
>;
|
||||
|
||||
export const EntityNamePickerSchema = {
|
||||
returnValue: zodToJsonSchema(
|
||||
EntityNamePickerReturnValueSchema,
|
||||
) as JSONSchema7,
|
||||
};
|
||||
@@ -24,19 +24,9 @@ import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import { EntityPickerReturnValue, EntityPickerUiOptions } from './schema';
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `EntityPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface EntityPickerUiOptions {
|
||||
allowedKinds?: string[];
|
||||
defaultKind?: string;
|
||||
allowArbitraryValues?: boolean;
|
||||
defaultNamespace?: string | false;
|
||||
}
|
||||
export { EntityPickerSchema } from './schema';
|
||||
|
||||
/**
|
||||
* The underlying component that is rendered in the form for the `EntityPicker`
|
||||
@@ -45,7 +35,10 @@ export interface EntityPickerUiOptions {
|
||||
* @public
|
||||
*/
|
||||
export const EntityPicker = (
|
||||
props: FieldExtensionComponentProps<string, EntityPickerUiOptions>,
|
||||
props: FieldExtensionComponentProps<
|
||||
EntityPickerReturnValue,
|
||||
EntityPickerUiOptions
|
||||
>,
|
||||
) => {
|
||||
const {
|
||||
onChange,
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export type { EntityPickerUiOptions } from './EntityPicker';
|
||||
export {
|
||||
type EntityPickerUiOptions,
|
||||
EntityPickerUiOptionsSchema,
|
||||
} from './schema';
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2022 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 { JSONSchema7 } from 'json-schema';
|
||||
import { z } from 'zod';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const EntityPickerUiOptionsSchema = z.object({
|
||||
allowedKinds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of kinds of entities to derive options from'),
|
||||
defaultKind: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The default entity kind. Options of this kind will not be prefixed.',
|
||||
),
|
||||
allowArbitraryValues: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to allow arbitrary user input. Defaults to true'),
|
||||
defaultNamespace: z
|
||||
.union([z.string(), z.literal(false)])
|
||||
.optional()
|
||||
.describe(
|
||||
'The default namespace. Options with this namespace will not be prefixed.',
|
||||
),
|
||||
});
|
||||
|
||||
const EntityPickerReturnValueSchema = z.string();
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `EntityPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityPickerUiOptions = z.infer<typeof EntityPickerUiOptionsSchema>;
|
||||
|
||||
export type EntityPickerReturnValue = z.infer<
|
||||
typeof EntityPickerReturnValueSchema
|
||||
>;
|
||||
|
||||
export const EntityPickerSchema = {
|
||||
uiOptions: zodToJsonSchema(EntityPickerUiOptionsSchema) as JSONSchema7,
|
||||
returnValue: zodToJsonSchema(EntityPickerReturnValueSchema) as JSONSchema7,
|
||||
};
|
||||
@@ -23,18 +23,12 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { FormControl, TextField } from '@material-ui/core';
|
||||
import { Autocomplete } from '@material-ui/lab';
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import {
|
||||
EntityTagsPickerReturnValue,
|
||||
EntityTagsPickerUiOptions,
|
||||
} from './schema';
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `EntityTagsPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface EntityTagsPickerUiOptions {
|
||||
kinds?: string[];
|
||||
showCounts?: boolean;
|
||||
helperText?: string;
|
||||
}
|
||||
export { EntityTagsPickerSchema } from './schema';
|
||||
|
||||
/**
|
||||
* The underlying component that is rendered in the form for the `EntityTagsPicker`
|
||||
@@ -43,7 +37,10 @@ export interface EntityTagsPickerUiOptions {
|
||||
* @public
|
||||
*/
|
||||
export const EntityTagsPicker = (
|
||||
props: FieldExtensionComponentProps<string[], EntityTagsPickerUiOptions>,
|
||||
props: FieldExtensionComponentProps<
|
||||
EntityTagsPickerReturnValue,
|
||||
EntityTagsPickerUiOptions
|
||||
>,
|
||||
) => {
|
||||
const { formData, onChange, uiSchema } = props;
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export type { EntityTagsPickerUiOptions } from './EntityTagsPicker';
|
||||
export {
|
||||
type EntityTagsPickerUiOptions,
|
||||
EntityTagsPickerUiOptionsSchema,
|
||||
} from './schema';
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2022 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 { JSONSchema7 } from 'json-schema';
|
||||
import { z } from 'zod';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const EntityTagsPickerUiOptionsSchema = z.object({
|
||||
kinds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of kinds of entities to derive tags from'),
|
||||
showCounts: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to show usage counts per tag'),
|
||||
helperText: z.string().optional().describe('Helper text to display'),
|
||||
});
|
||||
|
||||
const EntityTagsPickerReturnValueSchema = z.array(z.string());
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `EntityTagsPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityTagsPickerUiOptions = z.infer<
|
||||
typeof EntityTagsPickerUiOptionsSchema
|
||||
>;
|
||||
|
||||
export type EntityTagsPickerReturnValue = z.infer<
|
||||
typeof EntityTagsPickerReturnValueSchema
|
||||
>;
|
||||
|
||||
export const EntityTagsPickerSchema = {
|
||||
uiOptions: zodToJsonSchema(EntityTagsPickerUiOptionsSchema) as JSONSchema7,
|
||||
returnValue: zodToJsonSchema(
|
||||
EntityTagsPickerReturnValueSchema,
|
||||
) as JSONSchema7,
|
||||
};
|
||||
@@ -27,20 +27,12 @@ import React, { useMemo } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import {
|
||||
OwnedEntityPickerReturnValue,
|
||||
OwnedEntityPickerUiOptions,
|
||||
} from './schema';
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `OwnedEntityPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface OwnedEntityPickerUiOptions {
|
||||
allowedKinds?: string[];
|
||||
defaultKind?: string;
|
||||
allowArbitraryValues?: boolean;
|
||||
defaultNamespace?: string | false;
|
||||
}
|
||||
|
||||
export { OwnedEntityPickerSchema } from './schema';
|
||||
/**
|
||||
* The underlying component that is rendered in the form for the `OwnedEntityPicker`
|
||||
* field extension.
|
||||
@@ -48,7 +40,10 @@ export interface OwnedEntityPickerUiOptions {
|
||||
* @public
|
||||
*/
|
||||
export const OwnedEntityPicker = (
|
||||
props: FieldExtensionComponentProps<string, OwnedEntityPickerUiOptions>,
|
||||
props: FieldExtensionComponentProps<
|
||||
OwnedEntityPickerReturnValue,
|
||||
OwnedEntityPickerUiOptions
|
||||
>,
|
||||
) => {
|
||||
const {
|
||||
onChange,
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export type { OwnedEntityPickerUiOptions } from './OwnedEntityPicker';
|
||||
export {
|
||||
type OwnedEntityPickerUiOptions,
|
||||
OwnedEntityPickerUiOptionsSchema,
|
||||
} from './schema';
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2021 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 { JSONSchema7 } from 'json-schema';
|
||||
import { z } from 'zod';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const OwnedEntityPickerUiOptionsSchema = z.object({
|
||||
allowedKinds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of kinds of entities to derive options from'),
|
||||
defaultKind: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The default entity kind. Options of this kind will not be prefixed.',
|
||||
),
|
||||
allowArbitraryValues: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to allow arbitrary user input. Defaults to true'),
|
||||
defaultNamespace: z
|
||||
.union([z.string(), z.literal(false)])
|
||||
.optional()
|
||||
.describe(
|
||||
'The default namespace. Options with this namespace will not be prefixed.',
|
||||
),
|
||||
});
|
||||
|
||||
const OwnedEntityPickerReturnValueSchema = z.string();
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `OwnedEntityPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type OwnedEntityPickerUiOptions = z.infer<
|
||||
typeof OwnedEntityPickerUiOptionsSchema
|
||||
>;
|
||||
|
||||
export type OwnedEntityPickerReturnValue = z.infer<
|
||||
typeof OwnedEntityPickerReturnValueSchema
|
||||
>;
|
||||
|
||||
export const OwnedEntityPickerSchema = {
|
||||
uiOptions: zodToJsonSchema(OwnedEntityPickerUiOptionsSchema) as JSONSchema7,
|
||||
returnValue: zodToJsonSchema(
|
||||
OwnedEntityPickerReturnValueSchema,
|
||||
) as JSONSchema7,
|
||||
};
|
||||
@@ -16,18 +16,9 @@
|
||||
import React from 'react';
|
||||
import { EntityPicker } from '../EntityPicker/EntityPicker';
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import { OwnerPickerReturnValue, OwnerPickerUiOptions } from './schema';
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `OwnerPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface OwnerPickerUiOptions {
|
||||
allowedKinds?: string[];
|
||||
allowArbitraryValues?: boolean;
|
||||
defaultNamespace?: string | false;
|
||||
}
|
||||
export { OwnerPickerSchema } from './schema';
|
||||
|
||||
/**
|
||||
* The underlying component that is rendered in the form for the `OwnerPicker`
|
||||
@@ -36,7 +27,10 @@ export interface OwnerPickerUiOptions {
|
||||
* @public
|
||||
*/
|
||||
export const OwnerPicker = (
|
||||
props: FieldExtensionComponentProps<string, OwnerPickerUiOptions>,
|
||||
props: FieldExtensionComponentProps<
|
||||
OwnerPickerReturnValue,
|
||||
OwnerPickerUiOptions
|
||||
>,
|
||||
) => {
|
||||
const {
|
||||
schema: { title = 'Owner', description = 'The owner of the component' },
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export type { OwnerPickerUiOptions } from './OwnerPicker';
|
||||
export {
|
||||
type OwnerPickerUiOptions,
|
||||
OwnerPickerUiOptionsSchema,
|
||||
} from './schema';
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2022 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 { JSONSchema7 } from 'json-schema';
|
||||
import { z } from 'zod';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const OwnerPickerUiOptionsSchema = z.object({
|
||||
allowedKinds: z
|
||||
.array(z.string())
|
||||
.default(['Group', 'User'])
|
||||
.optional()
|
||||
.describe(
|
||||
'List of kinds of entities to derive options from. Defaults to Group and User',
|
||||
),
|
||||
allowArbitraryValues: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to allow arbitrary user input. Defaults to true'),
|
||||
defaultNamespace: z
|
||||
.union([z.string(), z.literal(false)])
|
||||
.optional()
|
||||
.describe(
|
||||
'The default namespace. Options with this namespace will not be prefixed.',
|
||||
),
|
||||
});
|
||||
|
||||
const OwnerPickerReturnValueSchema = z.string();
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `OwnerPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type OwnerPickerUiOptions = z.infer<typeof OwnerPickerUiOptionsSchema>;
|
||||
|
||||
export type OwnerPickerReturnValue = z.infer<
|
||||
typeof OwnerPickerReturnValueSchema
|
||||
>;
|
||||
|
||||
export const OwnerPickerSchema = {
|
||||
uiOptions: zodToJsonSchema(OwnerPickerUiOptionsSchema) as JSONSchema7,
|
||||
returnValue: zodToJsonSchema(OwnerPickerReturnValueSchema) as JSONSchema7,
|
||||
};
|
||||
@@ -28,32 +28,12 @@ import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import { RepoUrlPickerHost } from './RepoUrlPickerHost';
|
||||
import { RepoUrlPickerRepoName } from './RepoUrlPickerRepoName';
|
||||
import { parseRepoPickerUrl, serializeRepoPickerUrl } from './utils';
|
||||
import { RepoUrlPickerReturnValue, RepoUrlPickerUiOptions } from './schema';
|
||||
import { RepoUrlPickerState } from './types';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import { useTemplateSecrets } from '../../secrets';
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `RepoUrlPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface RepoUrlPickerUiOptions {
|
||||
allowedHosts?: string[];
|
||||
allowedOrganizations?: string[];
|
||||
allowedOwners?: string[];
|
||||
allowedRepos?: string[];
|
||||
requestUserCredentials?: {
|
||||
secretsKey: string;
|
||||
additionalScopes?: {
|
||||
gerrit?: string[];
|
||||
github?: string[];
|
||||
gitlab?: string[];
|
||||
bitbucket?: string[];
|
||||
azure?: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
export { RepoUrlPickerSchema } from './schema';
|
||||
|
||||
/**
|
||||
* The underlying component that is rendered in the form for the `RepoUrlPicker`
|
||||
@@ -62,7 +42,10 @@ export interface RepoUrlPickerUiOptions {
|
||||
* @public
|
||||
*/
|
||||
export const RepoUrlPicker = (
|
||||
props: FieldExtensionComponentProps<string, RepoUrlPickerUiOptions>,
|
||||
props: FieldExtensionComponentProps<
|
||||
RepoUrlPickerReturnValue,
|
||||
RepoUrlPickerUiOptions
|
||||
>,
|
||||
) => {
|
||||
const { uiSchema, onChange, rawErrors, formData } = props;
|
||||
const [state, setState] = useState<RepoUrlPickerState>(
|
||||
|
||||
@@ -13,5 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export type { RepoUrlPickerUiOptions } from './RepoUrlPicker';
|
||||
export {
|
||||
type RepoUrlPickerUiOptions,
|
||||
RepoUrlPickerUiOptionsSchema,
|
||||
} from './schema';
|
||||
export { repoPickerValidation } from './validation';
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2022 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 { JSONSchema7 } from 'json-schema';
|
||||
import { z } from 'zod';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const RepoUrlPickerUiOptionsSchema = z.object({
|
||||
allowedHosts: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of allowed SCM platform hosts'),
|
||||
allowedOrganizations: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of allowed organizations in the given SCM platform'),
|
||||
allowedOwners: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of allowed owners in the given SCM platform'),
|
||||
allowedRepos: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of allowed repos in the given SCM platform'),
|
||||
requestUserCredentials: z
|
||||
.object({
|
||||
secretsKey: z
|
||||
.string()
|
||||
.describe(
|
||||
'Key used within the template secrets context to store the credential',
|
||||
),
|
||||
additionalScopes: z
|
||||
.object({
|
||||
gerrit: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional Gerrit scopes to request'),
|
||||
github: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional GitHub scopes to request'),
|
||||
gitlab: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional GitLab scopes to request'),
|
||||
bitbucket: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional BitBucket scopes to request'),
|
||||
azure: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional Azure scopes to request'),
|
||||
})
|
||||
.optional()
|
||||
.describe('Additional permission scopes to request'),
|
||||
})
|
||||
.optional()
|
||||
.describe(
|
||||
'If defined will request user credentials to auth against the given SCM platform',
|
||||
),
|
||||
});
|
||||
|
||||
const RepoUrlPickerReturnValueSchema = z.string();
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `RepoUrlPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type RepoUrlPickerUiOptions = z.infer<
|
||||
typeof RepoUrlPickerUiOptionsSchema
|
||||
>;
|
||||
|
||||
export type RepoUrlPickerReturnValue = z.infer<
|
||||
typeof RepoUrlPickerReturnValueSchema
|
||||
>;
|
||||
|
||||
// NOTE: There is a bug with this failing validation in the custom field explorer due
|
||||
// to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if
|
||||
// requestUserCredentials is not defined
|
||||
export const RepoUrlPickerSchema = {
|
||||
uiOptions: zodToJsonSchema(RepoUrlPickerUiOptionsSchema) as JSONSchema7,
|
||||
returnValue: zodToJsonSchema(RepoUrlPickerReturnValueSchema) as JSONSchema7,
|
||||
};
|
||||
@@ -13,40 +13,64 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EntityPicker } from '../components/fields/EntityPicker/EntityPicker';
|
||||
import { EntityNamePicker } from '../components/fields/EntityNamePicker/EntityNamePicker';
|
||||
import {
|
||||
EntityPicker,
|
||||
EntityPickerSchema,
|
||||
} from '../components/fields/EntityPicker/EntityPicker';
|
||||
import {
|
||||
EntityNamePicker,
|
||||
EntityNamePickerSchema,
|
||||
} from '../components/fields/EntityNamePicker/EntityNamePicker';
|
||||
import { entityNamePickerValidation } from '../components/fields/EntityNamePicker/validation';
|
||||
import { EntityTagsPicker } from '../components/fields/EntityTagsPicker/EntityTagsPicker';
|
||||
import { OwnerPicker } from '../components/fields/OwnerPicker/OwnerPicker';
|
||||
import { RepoUrlPicker } from '../components/fields/RepoUrlPicker/RepoUrlPicker';
|
||||
import {
|
||||
EntityTagsPicker,
|
||||
EntityTagsPickerSchema,
|
||||
} from '../components/fields/EntityTagsPicker/EntityTagsPicker';
|
||||
import {
|
||||
OwnerPicker,
|
||||
OwnerPickerSchema,
|
||||
} from '../components/fields/OwnerPicker/OwnerPicker';
|
||||
import {
|
||||
RepoUrlPicker,
|
||||
RepoUrlPickerSchema,
|
||||
} from '../components/fields/RepoUrlPicker/RepoUrlPicker';
|
||||
import { repoPickerValidation } from '../components/fields/RepoUrlPicker/validation';
|
||||
import { OwnedEntityPicker } from '../components/fields/OwnedEntityPicker/OwnedEntityPicker';
|
||||
import {
|
||||
OwnedEntityPicker,
|
||||
OwnedEntityPickerSchema,
|
||||
} from '../components/fields/OwnedEntityPicker/OwnedEntityPicker';
|
||||
|
||||
export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
|
||||
{
|
||||
component: EntityPicker,
|
||||
name: 'EntityPicker',
|
||||
schema: EntityPickerSchema,
|
||||
},
|
||||
{
|
||||
component: EntityNamePicker,
|
||||
name: 'EntityNamePicker',
|
||||
validation: entityNamePickerValidation,
|
||||
schema: EntityNamePickerSchema,
|
||||
},
|
||||
{
|
||||
component: EntityTagsPicker,
|
||||
name: 'EntityTagsPicker',
|
||||
schema: EntityTagsPickerSchema,
|
||||
},
|
||||
{
|
||||
component: RepoUrlPicker,
|
||||
name: 'RepoUrlPicker',
|
||||
validation: repoPickerValidation,
|
||||
schema: RepoUrlPickerSchema,
|
||||
},
|
||||
{
|
||||
component: OwnerPicker,
|
||||
name: 'OwnerPicker',
|
||||
schema: OwnerPickerSchema,
|
||||
},
|
||||
{
|
||||
component: OwnedEntityPicker,
|
||||
name: 'OwnedEntityPicker',
|
||||
schema: OwnedEntityPickerSchema,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
CustomFieldExtensionSchema,
|
||||
CustomFieldValidator,
|
||||
FieldExtensionOptions,
|
||||
FieldExtensionComponentProps,
|
||||
@@ -103,6 +104,7 @@ attachComponentData(
|
||||
);
|
||||
|
||||
export type {
|
||||
CustomFieldExtensionSchema,
|
||||
CustomFieldValidator,
|
||||
FieldExtensionOptions,
|
||||
FieldExtensionComponentProps,
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { FieldValidation, FieldProps } from '@rjsf/core';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import {
|
||||
UIOptionsType,
|
||||
FieldProps as FieldPropsV5,
|
||||
UiSchema as UiSchemaV5,
|
||||
FieldValidation as FieldValidationV5,
|
||||
} from '@rjsf/utils';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
|
||||
/**
|
||||
* Field validation type for Custom Field Extensions.
|
||||
@@ -35,6 +35,16 @@ export type CustomFieldValidator<TFieldReturnValue> = (
|
||||
context: { apiHolder: ApiHolder },
|
||||
) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Type for the Custom Field Extension schema.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CustomFieldExtensionSchema = {
|
||||
uiOptions?: JSONSchema7;
|
||||
returnValue?: JSONSchema7;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type for the Custom Field Extension with the
|
||||
* name and components and validation function.
|
||||
@@ -50,6 +60,7 @@ export type FieldExtensionOptions<
|
||||
props: FieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
|
||||
) => JSX.Element | null;
|
||||
validation?: CustomFieldValidator<TFieldReturnValue>;
|
||||
schema?: CustomFieldExtensionSchema;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -107,4 +118,5 @@ export type NextFieldExtensionOptions<
|
||||
props: NextFieldExtensionComponentProps<TFieldReturnValue, TInputProps>,
|
||||
) => JSX.Element | null;
|
||||
validation?: NextCustomFieldValidator<TFieldReturnValue>;
|
||||
schema?: CustomFieldExtensionSchema;
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@ export {
|
||||
ScaffolderFieldExtensions,
|
||||
} from './extensions';
|
||||
export type {
|
||||
CustomFieldExtensionSchema,
|
||||
CustomFieldValidator,
|
||||
FieldExtensionOptions,
|
||||
FieldExtensionComponentProps,
|
||||
|
||||
@@ -16,12 +16,24 @@
|
||||
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { scaffolderApiRef, ScaffolderClient } from './api';
|
||||
import { EntityPicker } from './components/fields/EntityPicker/EntityPicker';
|
||||
import {
|
||||
EntityPicker,
|
||||
EntityPickerSchema,
|
||||
} from './components/fields/EntityPicker/EntityPicker';
|
||||
import { entityNamePickerValidation } from './components/fields/EntityNamePicker';
|
||||
import { EntityNamePicker } from './components/fields/EntityNamePicker/EntityNamePicker';
|
||||
import { OwnerPicker } from './components/fields/OwnerPicker/OwnerPicker';
|
||||
import {
|
||||
EntityNamePicker,
|
||||
EntityNamePickerSchema,
|
||||
} from './components/fields/EntityNamePicker/EntityNamePicker';
|
||||
import {
|
||||
OwnerPicker,
|
||||
OwnerPickerSchema,
|
||||
} from './components/fields/OwnerPicker/OwnerPicker';
|
||||
import { repoPickerValidation } from './components/fields/RepoUrlPicker';
|
||||
import { RepoUrlPicker } from './components/fields/RepoUrlPicker/RepoUrlPicker';
|
||||
import {
|
||||
RepoUrlPicker,
|
||||
RepoUrlPickerSchema,
|
||||
} from './components/fields/RepoUrlPicker/RepoUrlPicker';
|
||||
import { createScaffolderFieldExtension } from './extensions';
|
||||
import {
|
||||
nextRouteRef,
|
||||
@@ -37,8 +49,14 @@ import {
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker/OwnedEntityPicker';
|
||||
import { EntityTagsPicker } from './components/fields/EntityTagsPicker/EntityTagsPicker';
|
||||
import {
|
||||
OwnedEntityPicker,
|
||||
OwnedEntityPickerSchema,
|
||||
} from './components/fields/OwnedEntityPicker/OwnedEntityPicker';
|
||||
import {
|
||||
EntityTagsPicker,
|
||||
EntityTagsPickerSchema,
|
||||
} from './components/fields/EntityTagsPicker/EntityTagsPicker';
|
||||
|
||||
/**
|
||||
* The main plugin export for the scaffolder.
|
||||
@@ -82,6 +100,7 @@ export const EntityPickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: EntityPicker,
|
||||
name: 'EntityPicker',
|
||||
schema: EntityPickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -95,6 +114,7 @@ export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
|
||||
component: EntityNamePicker,
|
||||
name: 'EntityNamePicker',
|
||||
validation: entityNamePickerValidation,
|
||||
schema: EntityNamePickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -109,6 +129,7 @@ export const RepoUrlPickerFieldExtension = scaffolderPlugin.provide(
|
||||
component: RepoUrlPicker,
|
||||
name: 'RepoUrlPicker',
|
||||
validation: repoPickerValidation,
|
||||
schema: RepoUrlPickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -121,6 +142,7 @@ export const OwnerPickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: OwnerPicker,
|
||||
name: 'OwnerPicker',
|
||||
schema: OwnerPickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -146,6 +168,7 @@ export const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: OwnedEntityPicker,
|
||||
name: 'OwnedEntityPicker',
|
||||
schema: OwnedEntityPickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -157,6 +180,7 @@ export const EntityTagsPickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: EntityTagsPicker,
|
||||
name: 'EntityTagsPicker',
|
||||
schema: EntityTagsPickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user