chore: updating template schema to support ui:backstage featureFlag

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-03-25 16:25:01 +01:00
parent d3e8169a62
commit 797c17eef7
3 changed files with 187 additions and 43 deletions
@@ -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);
@@ -49,9 +49,17 @@ describe('useTemplateSchema', () => {
],
};
const {
steps: [first, second],
} = useTemplateSchema(manifest);
const { result } = renderHook(() => useTemplateSchema(manifest), {
wrapper: ({ children }) => (
<TestApiProvider
apis={[[featureFlagsApiRef, { isActive: () => false }]]}
>
{children}
</TestApiProvider>
),
});
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 }) => (
<TestApiProvider
apis={[[featureFlagsApiRef, { isActive: () => false }]]}
>
{children}
</TestApiProvider>
),
const { result } = renderHook(() => useTemplateSchema(manifest), {
wrapper: ({ children }) => (
<TestApiProvider
apis={[[featureFlagsApiRef, { isActive: () => false }]]}
>
{children}
</TestApiProvider>
),
});
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 }) => (
<TestApiProvider
apis={[[featureFlagsApiRef, { isActive: () => true }]]}
>
{children}
</TestApiProvider>
),
});
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 }) => (
<TestApiProvider
apis={[[featureFlagsApiRef, { isActive: () => false }]]}
>
{children}
</TestApiProvider>
),
});
const [first] = result.current.steps;
expect(first.schema).toEqual({
type: 'object',
properties: {
visibleField: {
type: 'string',
},
},
});
});
});
});
@@ -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,
};
};