From 92cf86a4b5deac2d82a6f0a9ef31d93935344fdc Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Thu, 9 Mar 2023 17:28:31 +0100 Subject: [PATCH 1/8] Filter out templates Signed-off-by: Bogdan Nechyporenko --- .changeset/ten-mayflies-beam.md | 5 ++ .../software-templates/configuration.md | 15 ++++ plugins/scaffolder/alpha-api-report.md | 5 ++ plugins/scaffolder/src/components/Router.tsx | 9 +- .../ScaffolderPage/ScaffolderPage.tsx | 6 ++ .../TemplateList/TemplateList.test.tsx | 83 +++++++++++++++++++ .../components/TemplateList/TemplateList.tsx | 8 +- plugins/scaffolder/src/next/Router/Router.tsx | 7 +- .../TemplateListPage/TemplateGroups.test.tsx | 48 +++++++++++ .../next/TemplateListPage/TemplateGroups.tsx | 6 +- .../TemplateListPage/TemplateListPage.tsx | 14 +++- 11 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 .changeset/ten-mayflies-beam.md create mode 100644 plugins/scaffolder/src/components/TemplateList/TemplateList.test.tsx diff --git a/.changeset/ten-mayflies-beam.md b/.changeset/ten-mayflies-beam.md new file mode 100644 index 0000000000..4fbb8416a8 --- /dev/null +++ b/.changeset/ten-mayflies-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Making it possible to hide software templates via templateFilter diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index ff5191bed2..24b8c530b8 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -93,3 +93,18 @@ You can also further customize groups by passing in a `titleComponent` instead of a `title` which will be a component to use as the header instead of just the default `ContentHeader` with the `title` set as it's value. ![Grouped Templates](../../assets/software-templates/grouped-templates.png) + +There is also an option to hide some templates. +You can have several use cases for that: + +- it's still in an experimental phase, so you can combine it with feature flagging for example +- you don't want to make them accessible from template list, but only open it on some action with pre-filled data. +- show different set of templates depends on target environment + +```typescript jsx + + entity?.metadata?.tags?.includes('experimental') ?? false + } +/> +``` diff --git a/plugins/scaffolder/alpha-api-report.md b/plugins/scaffolder/alpha-api-report.md index 1fc77322bd..f842ff35e1 100644 --- a/plugins/scaffolder/alpha-api-report.md +++ b/plugins/scaffolder/alpha-api-report.md @@ -31,6 +31,7 @@ export type NextRouterProps = { }>; }; groups?: TemplateGroupFilter[]; + templateFilter?: TemplatetemplateFilterFilter; FormProps?: FormProps_2; contextMenu?: { editor?: boolean; @@ -50,5 +51,9 @@ export type TemplateGroupFilter = { filter: (entity: Entity) => boolean; }; +// Warnings were encountered during analysis: +// +// src/next/Router/Router.d.ts:22:5 - (ae-forgotten-export) The symbol "TemplatetemplateFilterFilter" needs to be exported by the entry point alpha.d.ts + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index ccc2bd283b..b84fb664bc 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -58,6 +58,7 @@ export type RouterProps = { title?: React.ReactNode; filter: (entity: Entity) => boolean; }>; + templateFilter?: (entity: Entity) => boolean; defaultPreviewTemplate?: string; headerOptions?: { pageTitleOverride?: string; @@ -81,7 +82,12 @@ export type RouterProps = { * @public */ export const Router = (props: RouterProps) => { - const { groups, components = {}, defaultPreviewTemplate } = props; + const { + groups, + templateFilter, + components = {}, + defaultPreviewTemplate, + } = props; const { ReviewStepComponent, TemplateCardComponent, TaskPageComponent } = components; @@ -126,6 +132,7 @@ export const Router = (props: RouterProps) => { element={ boolean; }>; + templateFilter?: (entity: Entity) => boolean; contextMenu?: { editor?: boolean; actions?: boolean; @@ -64,6 +65,7 @@ export type ScaffolderPageProps = { export const ScaffolderPageContents = ({ TemplateCardComponent, groups, + templateFilter, contextMenu, headerOptions, }: ScaffolderPageProps) => { @@ -123,11 +125,13 @@ export const ScaffolderPageContents = ({ key={index} TemplateCardComponent={TemplateCardComponent} group={group} + templateFilter={templateFilter} /> ))} @@ -140,6 +144,7 @@ export const ScaffolderPageContents = ({ export const ScaffolderPage = ({ TemplateCardComponent, groups, + templateFilter, contextMenu, headerOptions, }: ScaffolderPageProps) => ( @@ -147,6 +152,7 @@ export const ScaffolderPage = ({ diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.test.tsx b/plugins/scaffolder/src/components/TemplateList/TemplateList.test.tsx new file mode 100644 index 0000000000..2ea417ad68 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateList/TemplateList.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright 2020 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 { screen } from '@testing-library/react'; +import React from 'react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { TemplateList } from './TemplateList'; +import { rootRouteRef } from '../../routes'; +import { + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from '@backstage/integration-react'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; + +jest.mock('@backstage/plugin-catalog-react', () => ({ + useEntityList: jest.fn().mockReturnValue({ + loading: false, + entities: [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't1', + }, + spec: {}, + }, + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't2', + }, + spec: {}, + }, + ], + }), + getEntityRelations: jest.fn().mockImplementation(() => []), + getEntitySourceLocation: jest.fn().mockImplementation(() => ({})), +})); + +describe('TemplateList', () => { + const mockIntegrationsApi: Partial = { + byHost: () => ({ type: 'github' }), + }; + + it('should filter out templates based on provided filter condition', async () => { + const TemplateCardComponent = ({ + template, + }: { + template: TemplateEntityV1beta3; + }) => ( +
{template.metadata.name}
+ ); + + await renderInTestApp( + +
+ e.metadata.name === 't1'} + TemplateCardComponent={TemplateCardComponent} + /> +
+
, + { mountedRoutes: { '/': rootRouteRef } }, + ); + + expect(() => screen.getByTestId('t1')).toThrow(); + expect(screen.getByTestId('t2')).toBeDefined(); + }); +}); diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx index 90d1c8cceb..190c0cd0ac 100644 --- a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx +++ b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx @@ -40,6 +40,7 @@ export type TemplateListProps = { title?: React.ReactNode; filter: (entity: Entity) => boolean; }; + templateFilter?: (entity: Entity) => boolean; }; /** @@ -48,12 +49,13 @@ export type TemplateListProps = { export const TemplateList = ({ TemplateCardComponent, group, + templateFilter, }: TemplateListProps) => { const { loading, error, entities } = useEntityList(); const Card = TemplateCardComponent || TemplateCard; - const maybeFilteredEntities = group - ? entities.filter(e => group.filter(e)) - : entities; + const maybeFilteredEntities = ( + group ? entities.filter(e => group.filter(e)) : entities + ).filter(e => (templateFilter ? !templateFilter(e) : true)); const titleComponent: React.ReactNode = (() => { if (group && group.title) { diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 5066e96d6c..8784cb7d0d 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -29,7 +29,10 @@ import { } from '@backstage/plugin-scaffolder-react'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups'; +import { + TemplateGroupFilter, + TemplatetemplateFilterFilter, +} from '../TemplateListPage/TemplateGroups'; import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../../extensions/default'; import { @@ -61,6 +64,7 @@ export type NextRouterProps = { }>; }; groups?: TemplateGroupFilter[]; + templateFilter?: TemplatetemplateFilterFilter; // todo(blam): rename this to formProps FormProps?: FormProps; contextMenu?: { @@ -111,6 +115,7 @@ export const Router = (props: PropsWithChildren) => { TemplateCardComponent={TemplateCardComponent} contextMenu={props.contextMenu} groups={props.groups} + templateFilter={props.templateFilter} /> } /> diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx index 2b66f294fa..291804abbd 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx @@ -207,4 +207,52 @@ describe('TemplateGroups', () => { {}, ); }); + + it('should filter out templates based on filter condition', async () => { + const mockEntities = [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't1', + }, + spec: {}, + }, + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't2', + }, + spec: {}, + }, + ]; + + (useEntityList as jest.Mock).mockReturnValue({ + entities: mockEntities, + loading: false, + error: null, + }); + + await renderInTestApp( + + true }]} + templateFilter={e => e.metadata.name === 't1'} + /> + , + { + mountedRoutes: { + '/next': nextRouteRef, + }, + }, + ); + + expect(TemplateGroup).toHaveBeenCalledWith( + expect.objectContaining({ + templates: [expect.objectContaining({ template: mockEntities[1] })], + }), + {}, + ); + }); }); diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx index 6f32bf38e5..a3e3467d06 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx @@ -34,6 +34,8 @@ import { TemplateGroup } from '@backstage/plugin-scaffolder-react/alpha'; import { viewTechDocRouteRef, selectedTemplateRouteRef } from '../../routes'; import { useNavigate } from 'react-router-dom'; +export type TemplatetemplateFilterFilter = (entity: Entity) => boolean; + /** * @alpha */ @@ -44,6 +46,7 @@ export type TemplateGroupFilter = { export interface TemplateGroupsProps { groups: TemplateGroupFilter[]; + templateFilter?: TemplatetemplateFilterFilter; TemplateCardComponent?: React.ComponentType<{ template: TemplateEntityV1beta3; }>; @@ -51,7 +54,7 @@ export interface TemplateGroupsProps { export const TemplateGroups = (props: TemplateGroupsProps) => { const { loading, error, entities } = useEntityList(); - const { groups, TemplateCardComponent } = props; + const { groups, templateFilter, TemplateCardComponent } = props; const errorApi = useApi(errorApiRef); const app = useApp(); const viewTechDocsLink = useRouteRef(viewTechDocRouteRef); @@ -90,6 +93,7 @@ export const TemplateGroups = (props: TemplateGroupsProps) => { <> {groups.map(({ title, filter }, index) => { const templates = entities + .filter(e => (templateFilter ? !templateFilter(e) : true)) .filter((e): e is TemplateEntityV1beta3 => filter(e)) .map(template => { const { kind, namespace, name } = parseEntityRef( diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx index bddc15a61b..fd90098b96 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -35,7 +35,11 @@ import { import { CategoryPicker } from './CategoryPicker'; import { RegisterExistingButton } from './RegisterExistingButton'; import { useRouteRef } from '@backstage/core-plugin-api'; -import { TemplateGroupFilter, TemplateGroups } from './TemplateGroups'; +import { + TemplateGroupFilter, + TemplateGroups, + TemplatetemplateFilterFilter, +} from './TemplateGroups'; import { registerComponentRouteRef } from '../../routes'; import { ContextMenu } from './ContextMenu'; @@ -44,6 +48,7 @@ export type TemplateListPageProps = { template: TemplateEntityV1beta3; }>; groups?: TemplateGroupFilter[]; + templateFilter?: TemplatetemplateFilterFilter; contextMenu?: { editor?: boolean; actions?: boolean; @@ -68,7 +73,11 @@ const createGroupsWithOther = ( export const TemplateListPage = (props: TemplateListPageProps) => { const registerComponentLink = useRouteRef(registerComponentRouteRef); - const { TemplateCardComponent, groups: givenGroups = [] } = props; + const { + TemplateCardComponent, + groups: givenGroups = [], + templateFilter, + } = props; const groups = givenGroups.length ? createGroupsWithOther(givenGroups) @@ -111,6 +120,7 @@ export const TemplateListPage = (props: TemplateListPageProps) => { From 161ac2d03a17ebd2b1858fcfde87c0b09fa59f16 Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Thu, 9 Mar 2023 17:29:58 +0100 Subject: [PATCH 2/8] Filter out templates Signed-off-by: Bogdan Nechyporenko --- .changeset/ten-mayflies-beam.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ten-mayflies-beam.md b/.changeset/ten-mayflies-beam.md index 4fbb8416a8..72effa620f 100644 --- a/.changeset/ten-mayflies-beam.md +++ b/.changeset/ten-mayflies-beam.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': minor --- -Making it possible to hide software templates via templateFilter +Making it possible to hide software templates via a template filter. From ff85fca3e9e70023a4fce86d162d20005a8a0073 Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Thu, 9 Mar 2023 17:36:39 +0100 Subject: [PATCH 3/8] Filter out templates Signed-off-by: Bogdan Nechyporenko --- plugins/scaffolder/alpha-api-report.md | 5 ----- plugins/scaffolder/api-report.md | 1 + plugins/scaffolder/src/alpha.ts | 1 + plugins/scaffolder/src/next/Router/Router.tsx | 7 ++----- .../src/next/TemplateListPage/TemplateGroups.tsx | 7 +++++-- .../src/next/TemplateListPage/TemplateListPage.tsx | 4 ++-- plugins/scaffolder/src/next/TemplateListPage/index.ts | 2 +- 7 files changed, 12 insertions(+), 15 deletions(-) diff --git a/plugins/scaffolder/alpha-api-report.md b/plugins/scaffolder/alpha-api-report.md index f842ff35e1..1fc77322bd 100644 --- a/plugins/scaffolder/alpha-api-report.md +++ b/plugins/scaffolder/alpha-api-report.md @@ -31,7 +31,6 @@ export type NextRouterProps = { }>; }; groups?: TemplateGroupFilter[]; - templateFilter?: TemplatetemplateFilterFilter; FormProps?: FormProps_2; contextMenu?: { editor?: boolean; @@ -51,9 +50,5 @@ export type TemplateGroupFilter = { filter: (entity: Entity) => boolean; }; -// Warnings were encountered during analysis: -// -// src/next/Router/Router.d.ts:22:5 - (ae-forgotten-export) The symbol "TemplatetemplateFilterFilter" needs to be exported by the entry point alpha.d.ts - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 9b39d14db4..3865a2517d 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -332,6 +332,7 @@ export type RouterProps = { title?: React_2.ReactNode; filter: (entity: Entity) => boolean; }>; + templateFilter?: (entity: Entity) => boolean; defaultPreviewTemplate?: string; headerOptions?: { pageTitleOverride?: string; diff --git a/plugins/scaffolder/src/alpha.ts b/plugins/scaffolder/src/alpha.ts index 72eb073fba..e1c2260acb 100644 --- a/plugins/scaffolder/src/alpha.ts +++ b/plugins/scaffolder/src/alpha.ts @@ -17,6 +17,7 @@ export { NextScaffolderPage } from './plugin'; export { type TemplateGroupFilter, + type TemplateFilter, type NextRouterProps, type FormProps, } from './next'; diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 8784cb7d0d..167801c255 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -29,10 +29,7 @@ import { } from '@backstage/plugin-scaffolder-react'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { - TemplateGroupFilter, - TemplatetemplateFilterFilter, -} from '../TemplateListPage/TemplateGroups'; +import { TemplateGroupFilter, TemplateFilter } from '../TemplateListPage'; import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../../extensions/default'; import { @@ -64,7 +61,7 @@ export type NextRouterProps = { }>; }; groups?: TemplateGroupFilter[]; - templateFilter?: TemplatetemplateFilterFilter; + templateFilter?: TemplateFilter; // todo(blam): rename this to formProps FormProps?: FormProps; contextMenu?: { diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx index a3e3467d06..decfeee470 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx @@ -34,7 +34,10 @@ import { TemplateGroup } from '@backstage/plugin-scaffolder-react/alpha'; import { viewTechDocRouteRef, selectedTemplateRouteRef } from '../../routes'; import { useNavigate } from 'react-router-dom'; -export type TemplatetemplateFilterFilter = (entity: Entity) => boolean; +/** + * @alpha + */ +export type TemplateFilter = (entity: Entity) => boolean; /** * @alpha @@ -46,7 +49,7 @@ export type TemplateGroupFilter = { export interface TemplateGroupsProps { groups: TemplateGroupFilter[]; - templateFilter?: TemplatetemplateFilterFilter; + templateFilter?: TemplateFilter; TemplateCardComponent?: React.ComponentType<{ template: TemplateEntityV1beta3; }>; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx index fd90098b96..3f81188e06 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -38,7 +38,7 @@ import { useRouteRef } from '@backstage/core-plugin-api'; import { TemplateGroupFilter, TemplateGroups, - TemplatetemplateFilterFilter, + TemplateFilter, } from './TemplateGroups'; import { registerComponentRouteRef } from '../../routes'; import { ContextMenu } from './ContextMenu'; @@ -48,7 +48,7 @@ export type TemplateListPageProps = { template: TemplateEntityV1beta3; }>; groups?: TemplateGroupFilter[]; - templateFilter?: TemplatetemplateFilterFilter; + templateFilter?: TemplateFilter; contextMenu?: { editor?: boolean; actions?: boolean; diff --git a/plugins/scaffolder/src/next/TemplateListPage/index.ts b/plugins/scaffolder/src/next/TemplateListPage/index.ts index 0436cfeeeb..a974052730 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/index.ts +++ b/plugins/scaffolder/src/next/TemplateListPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ export { TemplateListPage } from './TemplateListPage'; -export type { TemplateGroupFilter } from './TemplateGroups'; +export type { TemplateGroupFilter, TemplateFilter } from './TemplateGroups'; From 4418bd7dec9a2fbc647bb9187bfd598cdf00ff04 Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Thu, 9 Mar 2023 20:40:27 +0100 Subject: [PATCH 4/8] Filter out templates Signed-off-by: Bogdan Nechyporenko --- .changeset/ten-mayflies-beam.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ten-mayflies-beam.md b/.changeset/ten-mayflies-beam.md index 72effa620f..038f9eec87 100644 --- a/.changeset/ten-mayflies-beam.md +++ b/.changeset/ten-mayflies-beam.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder': minor +'@backstage/plugin-scaffolder': patch --- Making it possible to hide software templates via a template filter. From 6b69593dbf6a6dabec5675e15573d34f21511f1e Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 17 Mar 2023 17:03:40 +0100 Subject: [PATCH 5/8] chore: fixing api-reports and merge conflicts Signed-off-by: blam --- plugins/scaffolder/alpha-api-report.md | 4 ++++ .../src/next/TemplateListPage/TemplateGroups.test.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/alpha-api-report.md b/plugins/scaffolder/alpha-api-report.md index 1fc77322bd..58d70e3c71 100644 --- a/plugins/scaffolder/alpha-api-report.md +++ b/plugins/scaffolder/alpha-api-report.md @@ -31,6 +31,7 @@ export type NextRouterProps = { }>; }; groups?: TemplateGroupFilter[]; + templateFilter?: TemplateFilter; FormProps?: FormProps_2; contextMenu?: { editor?: boolean; @@ -44,6 +45,9 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; +// @alpha (undocumented) +export type TemplateFilter = (entity: Entity) => boolean; + // @alpha (undocumented) export type TemplateGroupFilter = { title?: React_2.ReactNode; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx index 291804abbd..c0fb503d9b 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx @@ -243,7 +243,7 @@ describe('TemplateGroups', () => { , { mountedRoutes: { - '/next': nextRouteRef, + '/create': rootRouteRef, }, }, ); From 70ae9f30db9e2d5f8a6be36952ac5604d6824b30 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 17 Mar 2023 17:06:32 +0100 Subject: [PATCH 6/8] chore: reworking changeset Signed-off-by: blam --- .changeset/ten-mayflies-beam.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/ten-mayflies-beam.md b/.changeset/ten-mayflies-beam.md index 038f9eec87..ff7d9e52e9 100644 --- a/.changeset/ten-mayflies-beam.md +++ b/.changeset/ten-mayflies-beam.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder': minor --- -Making it possible to hide software templates via a template filter. +Added a `templateFilter` prop to the `` component to allow for filtering of templates through a function. From 40a0a194342f2a42fb3923035ad1c33510e041ce Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 19 Mar 2023 13:05:16 +0100 Subject: [PATCH 7/8] chore: don't have a type for `TemplateFilter` Signed-off-by: blam --- plugins/scaffolder/alpha-api-report.md | 8 ++----- plugins/scaffolder/api-report.md | 2 +- plugins/scaffolder/src/alpha.ts | 1 - plugins/scaffolder/src/components/Router.tsx | 2 +- .../ScaffolderPage/ScaffolderPage.tsx | 7 +++--- .../components/TemplateList/TemplateList.tsx | 22 +++++++++++++++---- plugins/scaffolder/src/next/Router/Router.tsx | 4 ++-- .../next/TemplateListPage/TemplateGroups.tsx | 22 ++++++++----------- .../TemplateListPage/TemplateListPage.tsx | 8 ++----- .../src/next/TemplateListPage/index.ts | 2 +- 10 files changed, 39 insertions(+), 39 deletions(-) diff --git a/plugins/scaffolder/alpha-api-report.md b/plugins/scaffolder/alpha-api-report.md index 58d70e3c71..abb0dc9e03 100644 --- a/plugins/scaffolder/alpha-api-report.md +++ b/plugins/scaffolder/alpha-api-report.md @@ -5,7 +5,6 @@ ```ts /// -import { Entity } from '@backstage/catalog-model'; import { FormProps as FormProps_2 } from '@backstage/plugin-scaffolder-react/alpha'; import type { FormProps as FormProps_3 } from '@rjsf/core-v5'; import { PropsWithChildren } from 'react'; @@ -31,7 +30,7 @@ export type NextRouterProps = { }>; }; groups?: TemplateGroupFilter[]; - templateFilter?: TemplateFilter; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; FormProps?: FormProps_2; contextMenu?: { editor?: boolean; @@ -45,13 +44,10 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; -// @alpha (undocumented) -export type TemplateFilter = (entity: Entity) => boolean; - // @alpha (undocumented) export type TemplateGroupFilter = { title?: React_2.ReactNode; - filter: (entity: Entity) => boolean; + filter: (entity: TemplateEntityV1beta3) => boolean; }; // (No @packageDocumentation comment for this package) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 3865a2517d..7173bbd3e6 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -332,7 +332,7 @@ export type RouterProps = { title?: React_2.ReactNode; filter: (entity: Entity) => boolean; }>; - templateFilter?: (entity: Entity) => boolean; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; defaultPreviewTemplate?: string; headerOptions?: { pageTitleOverride?: string; diff --git a/plugins/scaffolder/src/alpha.ts b/plugins/scaffolder/src/alpha.ts index e1c2260acb..72eb073fba 100644 --- a/plugins/scaffolder/src/alpha.ts +++ b/plugins/scaffolder/src/alpha.ts @@ -17,7 +17,6 @@ export { NextScaffolderPage } from './plugin'; export { type TemplateGroupFilter, - type TemplateFilter, type NextRouterProps, type FormProps, } from './next'; diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index b84fb664bc..05d5cc9f0a 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -58,7 +58,7 @@ export type RouterProps = { title?: React.ReactNode; filter: (entity: Entity) => boolean; }>; - templateFilter?: (entity: Entity) => boolean; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; defaultPreviewTemplate?: string; headerOptions?: { pageTitleOverride?: string; diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index b56ebd83af..40435573e9 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -22,7 +22,6 @@ import { Page, SupportButton, } from '@backstage/core-components'; -import { Entity } from '@backstage/catalog-model'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { useRouteRef } from '@backstage/core-plugin-api'; import { @@ -47,9 +46,9 @@ export type ScaffolderPageProps = { | undefined; groups?: Array<{ title?: React.ReactNode; - filter: (entity: Entity) => boolean; + filter: (entity: TemplateEntityV1beta3) => boolean; }>; - templateFilter?: (entity: Entity) => boolean; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; contextMenu?: { editor?: boolean; actions?: boolean; @@ -72,7 +71,7 @@ export const ScaffolderPageContents = ({ const registerComponentLink = useRouteRef(registerComponentRouteRef); const otherTemplatesGroup = { title: groups ? 'Other Templates' : 'Templates', - filter: (entity: Entity) => { + filter: (entity: TemplateEntityV1beta3) => { const filtered = (groups ?? []).map(group => group.filter(entity)); return !filtered.some(result => result === true); }, diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx index 190c0cd0ac..cf1ee66810 100644 --- a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx +++ b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx @@ -38,9 +38,9 @@ export type TemplateListProps = { | undefined; group?: { title?: React.ReactNode; - filter: (entity: Entity) => boolean; + filter: (entity: TemplateEntityV1beta3) => boolean; }; - templateFilter?: (entity: Entity) => boolean; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; }; /** @@ -54,8 +54,22 @@ export const TemplateList = ({ const { loading, error, entities } = useEntityList(); const Card = TemplateCardComponent || TemplateCard; const maybeFilteredEntities = ( - group ? entities.filter(e => group.filter(e)) : entities - ).filter(e => (templateFilter ? !templateFilter(e) : true)); + group + ? entities + .filter( + (e): e is TemplateEntityV1beta3 => + e.kind === 'Template' && + e.apiVersion === 'scaffolder.backstage.io/v1beta3', + ) + .filter(group.filter) + : entities + ) + .filter( + (e): e is TemplateEntityV1beta3 => + e.kind === 'Template' && + e.apiVersion === 'scaffolder.backstage.io/v1beta3', + ) + .filter(e => (templateFilter ? !templateFilter(e) : true)); const titleComponent: React.ReactNode = (() => { if (group && group.title) { diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 167801c255..75d87602e4 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -29,7 +29,7 @@ import { } from '@backstage/plugin-scaffolder-react'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TemplateGroupFilter, TemplateFilter } from '../TemplateListPage'; +import { TemplateGroupFilter } from '../TemplateListPage'; import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../../extensions/default'; import { @@ -61,7 +61,7 @@ export type NextRouterProps = { }>; }; groups?: TemplateGroupFilter[]; - templateFilter?: TemplateFilter; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; // todo(blam): rename this to formProps FormProps?: FormProps; contextMenu?: { diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx index decfeee470..1096553352 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx @@ -15,11 +15,7 @@ */ import React, { useCallback } from 'react'; -import { - Entity, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { useEntityList } from '@backstage/plugin-catalog-react'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { Progress, Link, DocsIcon } from '@backstage/core-components'; @@ -34,22 +30,17 @@ import { TemplateGroup } from '@backstage/plugin-scaffolder-react/alpha'; import { viewTechDocRouteRef, selectedTemplateRouteRef } from '../../routes'; import { useNavigate } from 'react-router-dom'; -/** - * @alpha - */ -export type TemplateFilter = (entity: Entity) => boolean; - /** * @alpha */ export type TemplateGroupFilter = { title?: React.ReactNode; - filter: (entity: Entity) => boolean; + filter: (entity: TemplateEntityV1beta3) => boolean; }; export interface TemplateGroupsProps { groups: TemplateGroupFilter[]; - templateFilter?: TemplateFilter; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; TemplateCardComponent?: React.ComponentType<{ template: TemplateEntityV1beta3; }>; @@ -96,8 +87,13 @@ export const TemplateGroups = (props: TemplateGroupsProps) => { <> {groups.map(({ title, filter }, index) => { const templates = entities + .filter( + (e): e is TemplateEntityV1beta3 => + e.kind === 'Template' && + e.apiVersion === 'scaffolder.backstage.io/v1beta3', + ) .filter(e => (templateFilter ? !templateFilter(e) : true)) - .filter((e): e is TemplateEntityV1beta3 => filter(e)) + .filter(filter) .map(template => { const { kind, namespace, name } = parseEntityRef( stringifyEntityRef(template), diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx index 3f81188e06..0af20db6c9 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -35,11 +35,7 @@ import { import { CategoryPicker } from './CategoryPicker'; import { RegisterExistingButton } from './RegisterExistingButton'; import { useRouteRef } from '@backstage/core-plugin-api'; -import { - TemplateGroupFilter, - TemplateGroups, - TemplateFilter, -} from './TemplateGroups'; +import { TemplateGroupFilter, TemplateGroups } from './TemplateGroups'; import { registerComponentRouteRef } from '../../routes'; import { ContextMenu } from './ContextMenu'; @@ -48,7 +44,7 @@ export type TemplateListPageProps = { template: TemplateEntityV1beta3; }>; groups?: TemplateGroupFilter[]; - templateFilter?: TemplateFilter; + templateFilter?: (entity: TemplateEntityV1beta3) => boolean; contextMenu?: { editor?: boolean; actions?: boolean; diff --git a/plugins/scaffolder/src/next/TemplateListPage/index.ts b/plugins/scaffolder/src/next/TemplateListPage/index.ts index a974052730..0436cfeeeb 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/index.ts +++ b/plugins/scaffolder/src/next/TemplateListPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ export { TemplateListPage } from './TemplateListPage'; -export type { TemplateGroupFilter, TemplateFilter } from './TemplateGroups'; +export type { TemplateGroupFilter } from './TemplateGroups'; From ceab3c402f39b6a6597ce90cbfaa1a5d3534d46b Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 20 Mar 2023 11:28:59 +0100 Subject: [PATCH 8/8] chore: refactoring some of the simplification Signed-off-by: blam --- .../components/TemplateList/TemplateList.tsx | 20 ++++------------ .../scaffolder/src/lib/isTemplateEntity.ts | 23 +++++++++++++++++++ .../next/TemplateListPage/TemplateGroups.tsx | 7 ++---- 3 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 plugins/scaffolder/src/lib/isTemplateEntity.ts diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx index cf1ee66810..9aa0ec5eef 100644 --- a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx +++ b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx @@ -28,6 +28,7 @@ import { import { useEntityList } from '@backstage/plugin-catalog-react'; import { Typography } from '@material-ui/core'; import { TemplateCard } from '../TemplateCard'; +import { isTemplateEntity } from '../../lib/isTemplateEntity'; /** * @internal @@ -53,23 +54,10 @@ export const TemplateList = ({ }: TemplateListProps) => { const { loading, error, entities } = useEntityList(); const Card = TemplateCardComponent || TemplateCard; + const templateEntities = entities.filter(isTemplateEntity); const maybeFilteredEntities = ( - group - ? entities - .filter( - (e): e is TemplateEntityV1beta3 => - e.kind === 'Template' && - e.apiVersion === 'scaffolder.backstage.io/v1beta3', - ) - .filter(group.filter) - : entities - ) - .filter( - (e): e is TemplateEntityV1beta3 => - e.kind === 'Template' && - e.apiVersion === 'scaffolder.backstage.io/v1beta3', - ) - .filter(e => (templateFilter ? !templateFilter(e) : true)); + group ? templateEntities.filter(group.filter) : templateEntities + ).filter(e => (templateFilter ? !templateFilter(e) : true)); const titleComponent: React.ReactNode = (() => { if (group && group.title) { diff --git a/plugins/scaffolder/src/lib/isTemplateEntity.ts b/plugins/scaffolder/src/lib/isTemplateEntity.ts new file mode 100644 index 0000000000..61ceabaa53 --- /dev/null +++ b/plugins/scaffolder/src/lib/isTemplateEntity.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2023 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 { Entity } from '@backstage/catalog-model'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; + +export const isTemplateEntity = ( + entity: Entity, +): entity is TemplateEntityV1beta3 => + entity.apiVersion === 'scaffolder.backstage.io/v1beta3' && + entity.kind === 'Template'; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx index 1096553352..79e2648d2f 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx @@ -29,6 +29,7 @@ import { import { TemplateGroup } from '@backstage/plugin-scaffolder-react/alpha'; import { viewTechDocRouteRef, selectedTemplateRouteRef } from '../../routes'; import { useNavigate } from 'react-router-dom'; +import { isTemplateEntity } from '../../lib/isTemplateEntity'; /** * @alpha @@ -87,11 +88,7 @@ export const TemplateGroups = (props: TemplateGroupsProps) => { <> {groups.map(({ title, filter }, index) => { const templates = entities - .filter( - (e): e is TemplateEntityV1beta3 => - e.kind === 'Template' && - e.apiVersion === 'scaffolder.backstage.io/v1beta3', - ) + .filter(isTemplateEntity) .filter(e => (templateFilter ? !templateFilter(e) : true)) .filter(filter) .map(template => {