From 797c17eef749fa7bdac1300a0db30014233962b3 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 25 Mar 2022 16:25:01 +0100 Subject: [PATCH] chore: updating template schema to support ui:backstage featureFlag Signed-off-by: blam --- .../next/TemplateWizardPage/Stepper/schema.ts | 5 +- .../Stepper/useTemplateSchema.test.tsx | 190 ++++++++++++++---- .../Stepper/useTemplateSchema.ts | 35 +++- 3 files changed, 187 insertions(+), 43 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts index bb4c56f7b0..5a33a622bb 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts @@ -15,7 +15,6 @@ */ import { JsonObject } from '@backstage/types'; import { UiSchema } from '@rjsf/core'; -import { JSONSchema7 } from 'json-schema'; function isObject(value: unknown): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); @@ -104,8 +103,8 @@ function extractUiSchema(schema: JsonObject, uiSchema: JsonObject) { * Takes a step from a Backstage Template Manifest and converts it to a JSON Schema and UI Schema for rjsf */ export const extractSchemaFromStep = ( - inputStep: JSONSchema7, -): { uiSchema: UiSchema; schema: JSONSchema7 } => { + inputStep: JsonObject, +): { uiSchema: UiSchema; schema: JsonObject } => { const uiSchema: UiSchema = {}; const returnSchema: JsonObject = JSON.parse(JSON.stringify(inputStep)); extractUiSchema(returnSchema, uiSchema); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.test.tsx index 18cdc97264..9be6faa0fa 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.test.tsx @@ -49,9 +49,17 @@ describe('useTemplateSchema', () => { ], }; - const { - steps: [first, second], - } = useTemplateSchema(manifest); + const { result } = renderHook(() => useTemplateSchema(manifest), { + wrapper: ({ children }) => ( + false }]]} + > + {children} + + ), + }); + + const [first, second] = result.current.steps; expect(first.uiSchema).toEqual({ field1: { 'ui:field': 'MyCoolComponent' }, @@ -76,45 +84,153 @@ describe('useTemplateSchema', () => { }); }); - it('should use featureFlags property to skip a step if the whole step is disabled', () => { - const manifest: TemplateParameterSchema = { - title: 'Test Template', - description: 'Test Template Description', - steps: [ - { - title: 'Step 1', - description: 'Step 1 Description', - schema: { - type: 'object', - 'ui:backstage': { - featureFlag: 'my-feature-flag', - }, - properties: { - field1: { type: 'string', 'ui:field': 'MyCoolComponent' }, + describe('FeatureFlags', () => { + it('should use featureFlags property to skip a step if the whole step is disabled', () => { + const manifest: TemplateParameterSchema = { + title: 'Test Template', + description: 'Test Template Description', + steps: [ + { + title: 'Step 1', + description: 'Step 1 Description', + schema: { + type: 'object', + 'ui:backstage': { + featureFlag: 'my-feature-flag', + }, + properties: { + field1: { type: 'string', 'ui:field': 'MyCoolComponent' }, + }, }, }, - }, - { - title: 'Step 2', - description: 'Step 2 Description', - schema: { - type: 'object', - properties: { - field2: { type: 'string', 'ui:field': 'MyCoolerComponent' }, + { + title: 'Step 2', + description: 'Step 2 Description', + schema: { + type: 'object', + properties: { + field2: { type: 'string', 'ui:field': 'MyCoolerComponent' }, + }, }, }, - }, - ], - }; + ], + }; - const result = renderHook(() => useTemplateSchema(manifest), { - wrapper: ({ children }) => ( - false }]]} - > - {children} - - ), + const { result } = renderHook(() => useTemplateSchema(manifest), { + wrapper: ({ children }) => ( + false }]]} + > + {children} + + ), + }); + + expect(result.current.steps).toHaveLength(1); + }); + + it('should use featureFlags property to enable a step if the whole step is enabled', () => { + const manifest: TemplateParameterSchema = { + title: 'Test Template', + description: 'Test Template Description', + steps: [ + { + title: 'Step 1', + description: 'Step 1 Description', + schema: { + type: 'object', + 'ui:backstage': { + featureFlag: 'my-feature-flag', + }, + properties: { + field1: { type: 'string', 'ui:field': 'MyCoolComponent' }, + }, + }, + }, + { + title: 'Step 2', + description: 'Step 2 Description', + schema: { + type: 'object', + properties: { + field2: { type: 'string', 'ui:field': 'MyCoolerComponent' }, + }, + }, + }, + ], + }; + + const { result } = renderHook(() => useTemplateSchema(manifest), { + wrapper: ({ children }) => ( + true }]]} + > + {children} + + ), + }); + + expect(result.current.steps).toHaveLength(2); + }); + + it('should filter out the particular property if the featureFlag is disabled', () => { + const manifest: TemplateParameterSchema = { + title: 'Test Template', + description: 'Test Template Description', + steps: [ + { + title: 'Step 1', + description: 'Step 1 Description', + schema: { + type: 'object', + properties: { + field1: { + type: 'string', + 'ui:field': 'MyCoolComponent', + 'ui:backstage': { + featureFlag: 'my-feature-flag', + }, + }, + visibleField: { + type: 'string', + 'ui:field': 'MyCoolComponent', + }, + }, + }, + }, + { + title: 'Step 2', + description: 'Step 2 Description', + schema: { + type: 'object', + properties: { + field2: { type: 'string', 'ui:field': 'MyCoolerComponent' }, + }, + }, + }, + ], + }; + + const { result } = renderHook(() => useTemplateSchema(manifest), { + wrapper: ({ children }) => ( + false }]]} + > + {children} + + ), + }); + + const [first] = result.current.steps; + + expect(first.schema).toEqual({ + type: 'object', + properties: { + visibleField: { + type: 'string', + }, + }, + }); }); }); }); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts index 38ea34048e..1fe6398228 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts @@ -13,15 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; +import { JsonObject } from '@backstage/types'; import { UiSchema } from '@rjsf/core'; -import { JSONSchema7 } from 'json-schema'; import { TemplateParameterSchema } from '../../../types'; import { extractSchemaFromStep } from './schema'; export const useTemplateSchema = ( manifest: TemplateParameterSchema, -): { steps: { uiSchema: UiSchema; schema: JSONSchema7 }[] } => { +): { steps: { uiSchema: UiSchema; schema: JsonObject }[] } => { + const featureFlags = useApi(featureFlagsApiRef); + const steps = manifest.steps.map(({ schema }) => + extractSchemaFromStep(schema), + ); + + const returningSteps = steps + .filter(step => { + const stepFeatureFlag = step.uiSchema['ui:backstage']?.featureFlag; + return stepFeatureFlag ? featureFlags.isActive(stepFeatureFlag) : true; + }) + .map(step => ({ + uiSchema: step.uiSchema, + schema: { + ...step.schema, + properties: Object.fromEntries( + Object.entries(step.schema.properties as JsonObject).filter( + ([key]) => { + const stepFeatureFlag = + step.uiSchema[key]?.['ui:backstage']?.featureFlag; + return stepFeatureFlag + ? featureFlags.isActive(stepFeatureFlag) + : true; + }, + ), + ), + }, + })); + return { - steps: manifest.steps.map(({ schema }) => extractSchemaFromStep(schema)), + steps: returningSteps, }; };