feat(scaffolder): remove sections or fields based on a feature flag
Signed-off-by: djamaile <rdjamaile@gmail.com>
This commit is contained in:
@@ -24,7 +24,11 @@ import {
|
||||
Stepper,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
errorApiRef,
|
||||
useApi,
|
||||
featureFlagsApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import React, { useState } from 'react';
|
||||
@@ -116,13 +120,53 @@ export const MultistepJsonForm = ({
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [disableButtons, setDisableButtons] = useState(false);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const featureFlagApi = useApi(featureFlagsApiRef);
|
||||
const featureFlagKey = 'backstage:featureFlag';
|
||||
const filterOutProperties = (schema: JsonObject): JsonObject => {
|
||||
const removedPropertiesKeys: Array<string> = [];
|
||||
if (schema.properties) {
|
||||
schema.properties = Object.fromEntries(
|
||||
Object.entries(schema.properties).filter(([key, value]) => {
|
||||
if (value[featureFlagKey]) {
|
||||
if (featureFlagApi.isActive(value[featureFlagKey])) {
|
||||
return true;
|
||||
}
|
||||
removedPropertiesKeys.push(key);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
|
||||
// remove the feature flag property keys from required if they are not active
|
||||
if (Array.isArray(schema.required) && removedPropertiesKeys.length > 0) {
|
||||
for (const property of removedPropertiesKeys) {
|
||||
const index = schema.required.findIndex(r => r === property);
|
||||
if (index > -1) {
|
||||
schema.required.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return schema;
|
||||
};
|
||||
|
||||
const updatedSteps = steps.filter(s => {
|
||||
const featureFlag = s.schema[featureFlagKey] as string;
|
||||
if (featureFlag && !featureFlagApi.isActive(featureFlag)) {
|
||||
return null;
|
||||
}
|
||||
// filter out properties accordingly to the feature flag settings;
|
||||
s.schema = filterOutProperties(s.schema);
|
||||
return s;
|
||||
});
|
||||
|
||||
const handleReset = () => {
|
||||
setActiveStep(0);
|
||||
onReset();
|
||||
};
|
||||
const handleNext = () => {
|
||||
setActiveStep(Math.min(activeStep + 1, steps.length));
|
||||
setActiveStep(Math.min(activeStep + 1, updatedSteps.length));
|
||||
};
|
||||
const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
|
||||
const handleCreate = async () => {
|
||||
@@ -138,7 +182,7 @@ export const MultistepJsonForm = ({
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
{steps.map(({ title, schema, ...formProps }, index) => {
|
||||
{updatedSteps.map(({ title, schema, ...formProps }, index) => {
|
||||
return (
|
||||
<StepUI key={title}>
|
||||
<StepLabel
|
||||
|
||||
@@ -26,6 +26,10 @@ import { MemoryRouter, Route } from 'react-router';
|
||||
import { ScaffolderApi, scaffolderApiRef } from '../../api';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { TemplatePage } from './TemplatePage';
|
||||
import {
|
||||
featureFlagsApiRef,
|
||||
FeatureFlagsApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { errorApiRef } from '@backstage/core-plugin-api';
|
||||
@@ -48,11 +52,63 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
listActions: jest.fn(),
|
||||
};
|
||||
|
||||
const featureFlagsApiMock: jest.Mocked<FeatureFlagsApi> = {
|
||||
isActive: jest.fn(),
|
||||
registerFlag: jest.fn(),
|
||||
getRegisteredFlags: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
|
||||
|
||||
const schemaMockValue = {
|
||||
title: 'my-schema',
|
||||
steps: [
|
||||
{
|
||||
title: 'Fill in some steps',
|
||||
schema: {
|
||||
title: 'Fill in some steps',
|
||||
'backstage:featureFlag': 'experimental-feature',
|
||||
properties: {
|
||||
name: {
|
||||
title: 'Name',
|
||||
type: 'string',
|
||||
'backstage:featureFlag': 'should-show-some-stuff-first-option',
|
||||
},
|
||||
description: {
|
||||
title: 'Description',
|
||||
type: 'string',
|
||||
description: 'A description for the component',
|
||||
},
|
||||
owner: {
|
||||
title: 'Owner',
|
||||
type: 'string',
|
||||
description: 'Owner of the component',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Send data',
|
||||
schema: {
|
||||
title: 'Send data',
|
||||
properties: {
|
||||
user: {
|
||||
title: 'User',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from(
|
||||
[scaffolderApiRef, scaffolderApiMock],
|
||||
[errorApiRef, errorApiMock],
|
||||
[featureFlagsApiRef, featureFlagsApiMock],
|
||||
);
|
||||
|
||||
describe('TemplatePage', () => {
|
||||
@@ -197,4 +253,32 @@ describe('TemplatePage', () => {
|
||||
fireEvent.click(await findByText('Next step'));
|
||||
expect(await findByText('Reset')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display a section or property based on a feature flag', async () => {
|
||||
featureFlagsApiMock.isActive.mockImplementation(flag => {
|
||||
if (flag === 'experimental-feature') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue(
|
||||
schemaMockValue,
|
||||
);
|
||||
|
||||
const { queryByText } = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TemplatePage />
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/create/actions': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(await queryByText('Name')).not.toBeInTheDocument();
|
||||
expect(await queryByText('Description')).toBeInTheDocument();
|
||||
expect(await queryByText('Owner')).toBeInTheDocument();
|
||||
expect(await queryByText('Send data')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user