scaffolder: implement uiSchema support in template parameter schema
Co-authored-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { Content, StructuredMetadataTable } from '@backstage/core';
|
||||
import {
|
||||
Box,
|
||||
@@ -28,10 +28,11 @@ import {
|
||||
import { FormProps, IChangeEvent, withTheme } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import React, { useState } from 'react';
|
||||
import { transformSchemaToProps } from './schema';
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
type Step = {
|
||||
schema: JSONSchema;
|
||||
schema: JsonObject;
|
||||
title: string;
|
||||
} & Partial<Omit<FormProps<any>, 'schema'>>;
|
||||
|
||||
@@ -66,35 +67,33 @@ export const MultistepJsonForm = ({
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
{steps.map(
|
||||
({ title, schema: { title: _, ...schema }, ...formProps }) => (
|
||||
<StepUI key={title}>
|
||||
<StepLabel>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
</StepLabel>
|
||||
<StepContent key={title}>
|
||||
<Form
|
||||
key={title}
|
||||
noHtml5Validate
|
||||
formData={formData}
|
||||
onChange={onChange}
|
||||
schema={schema as FormProps<any>['schema']}
|
||||
onSubmit={e => {
|
||||
if (e.errors.length === 0) handleNext();
|
||||
}}
|
||||
{...formProps}
|
||||
>
|
||||
<Button disabled={activeStep === 0} onClick={handleBack}>
|
||||
Back
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" type="submit">
|
||||
Next step
|
||||
</Button>
|
||||
</Form>
|
||||
</StepContent>
|
||||
</StepUI>
|
||||
),
|
||||
)}
|
||||
{steps.map(({ title, schema, ...formProps }) => (
|
||||
<StepUI key={title}>
|
||||
<StepLabel>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
</StepLabel>
|
||||
<StepContent key={title}>
|
||||
<Form
|
||||
key={title}
|
||||
noHtml5Validate
|
||||
formData={formData}
|
||||
onChange={onChange}
|
||||
onSubmit={e => {
|
||||
if (e.errors.length === 0) handleNext();
|
||||
}}
|
||||
{...formProps}
|
||||
{...transformSchemaToProps(schema)}
|
||||
>
|
||||
<Button disabled={activeStep === 0} onClick={handleBack}>
|
||||
Back
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" type="submit">
|
||||
Next step
|
||||
</Button>
|
||||
</Form>
|
||||
</StepContent>
|
||||
</StepUI>
|
||||
))}
|
||||
</Stepper>
|
||||
{activeStep === steps.length && (
|
||||
<Content>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2020 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 { transformSchemaToProps } from './schema';
|
||||
|
||||
describe('transformSchemaToProps', () => {
|
||||
it('transforms deep schema', () => {
|
||||
const inputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
'ui:derp': 'herp',
|
||||
},
|
||||
field2: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fieldX: {
|
||||
type: 'string',
|
||||
'ui:derp': 'xerp',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const expectedSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
field1: {
|
||||
type: 'string',
|
||||
},
|
||||
field2: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fieldX: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const expectedUiSchema = {
|
||||
field1: {
|
||||
'ui:derp': 'herp',
|
||||
},
|
||||
field2: {
|
||||
fieldX: {
|
||||
'ui:derp': 'xerp',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(transformSchemaToProps(inputSchema)).toEqual({
|
||||
schema: expectedSchema,
|
||||
uiSchema: expectedUiSchema,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 { JsonObject } from '@backstage/config';
|
||||
import { FormProps } from '@rjsf/core';
|
||||
|
||||
function isObject(value: unknown): value is JsonObject {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function extractUiSchema(schema: JsonObject, uiSchema: JsonObject) {
|
||||
const { properties } = schema;
|
||||
if (!isObject(properties)) {
|
||||
return;
|
||||
}
|
||||
for (const propName in properties) {
|
||||
if (!properties.hasOwnProperty(propName)) {
|
||||
continue;
|
||||
}
|
||||
const schemaNode = properties[propName];
|
||||
if (!isObject(schemaNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (schemaNode.type === 'object') {
|
||||
const innerUiSchema = {};
|
||||
uiSchema[propName] = innerUiSchema;
|
||||
extractUiSchema(schemaNode, innerUiSchema);
|
||||
} else {
|
||||
for (const innerKey in schemaNode) {
|
||||
if (!schemaNode.hasOwnProperty(innerKey)) {
|
||||
continue;
|
||||
}
|
||||
const innerValue = schemaNode[innerKey];
|
||||
if (innerKey.startsWith('ui:')) {
|
||||
const innerUiSchema = uiSchema[propName] || {};
|
||||
if (!isObject(innerUiSchema)) {
|
||||
throw new TypeError('Unexpected non-object in uiSchema');
|
||||
}
|
||||
uiSchema[propName] = innerUiSchema;
|
||||
|
||||
innerUiSchema[innerKey] = innerValue;
|
||||
delete schemaNode[innerKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function transformSchemaToProps(
|
||||
inputSchema: JsonObject,
|
||||
): { schema: FormProps<any>['schema']; uiSchema: FormProps<any>['uiSchema'] } {
|
||||
const schema = JSON.parse(JSON.stringify(inputSchema));
|
||||
delete schema.title; // Rendered separately
|
||||
const uiSchema = {};
|
||||
extractUiSchema(schema, uiSchema);
|
||||
return { schema, uiSchema };
|
||||
}
|
||||
Reference in New Issue
Block a user