From f780859eb0691fc254e8547eb68cddb51ceb7dbb Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 9 Jun 2022 16:12:16 +0200 Subject: [PATCH 1/4] feat: added the ability to run namespaced templates Signed-off-by: blam --- plugins/scaffolder-backend/src/service/helpers.ts | 5 ----- plugins/scaffolder/src/components/Router.tsx | 14 ++++++++++++-- .../src/components/TaskPage/TaskPage.tsx | 4 ++-- .../src/components/TemplateCard/TemplateCard.tsx | 10 ++++++++-- .../src/components/TemplatePage/TemplatePage.tsx | 15 ++++++++------- .../scaffolder/src/next/Router/Router.test.tsx | 6 ++++-- .../TemplateCard/TemplateCard.test.tsx | 2 +- .../TemplateCard/TemplateCard.tsx | 9 +++++++-- plugins/scaffolder/src/routes.ts | 8 +++++++- 9 files changed, 49 insertions(+), 24 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index 23200d6aac..13197861a8 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -95,11 +95,6 @@ export async function findTemplate(options: { }): Promise { const { entityRef, token, catalogApi } = options; - if (entityRef.namespace.toLocaleLowerCase('en-US') !== DEFAULT_NAMESPACE) { - throw new InputError( - `Invalid namespace, only '${DEFAULT_NAMESPACE}' namespace is supported`, - ); - } if (entityRef.kind.toLocaleLowerCase('en-US') !== 'template') { throw new InputError(`Invalid kind, only 'Template' kind is supported`); } diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 6beaa3419a..172a21234a 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -15,7 +15,7 @@ */ import React, { ComponentType } from 'react'; -import { Routes, Route, useOutlet, Navigate } from 'react-router'; +import { Routes, Route, useOutlet, Navigate, useParams } from 'react-router'; import { Entity } from '@backstage/catalog-model'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { ScaffolderPage } from './ScaffolderPage'; @@ -31,10 +31,11 @@ import { FIELD_EXTENSION_KEY, DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, } from '../extensions'; -import { useElementFilter } from '@backstage/core-plugin-api'; +import { useElementFilter, useRouteRef } from '@backstage/core-plugin-api'; import { actionsRouteRef, editRouteRef, + legacySelectedTemplateRouteRef, scaffolderListTaskRouteRef, scaffolderTaskRouteRef, selectedTemplateRouteRef, @@ -101,6 +102,12 @@ export const Router = (props: RouterProps) => { ), ]; + const RedirectingComponent = () => { + const { templateName } = useParams(); + const newLink = useRouteRef(selectedTemplateRouteRef); + return ; + }; + return ( { /> } /> + + + { const formData = taskStream.task!.spec.parameters; - const { name } = parseEntityRef( + const { name, namespace } = parseEntityRef( taskStream.task!.spec.templateInfo?.entityRef, ); navigate( - `${templateRoute({ templateName: name })}?${qs.stringify({ + `${templateRoute({ templateName: name, namespace })}?${qs.stringify({ formData: JSON.stringify(formData), })}`, ); diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 6f7e375cce..df46a119f2 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { + Entity, + parseEntityRef, + RELATION_OWNED_BY, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { ScmIntegrationIcon, @@ -162,7 +167,8 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { : 'other'; const theme = backstageTheme.getPageTheme({ themeId }); const classes = useStyles({ backgroundImage: theme.backgroundImage }); - const href = templateRoute({ templateName: templateProps.name }); + const { name, namespace } = parseEntityRef(stringifyEntityRef(template)); + const href = templateRoute({ templateName: name, namespace }); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi); diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 22f995eae4..f694688801 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -54,11 +54,16 @@ export const TemplatePage = ({ const secretsContext = useContext(SecretsContext); const errorApi = useApi(errorApiRef); const scaffolderApi = useApi(scaffolderApiRef); - const { templateName } = useParams(); + const { templateName, namespace } = useParams(); + const templateRef = stringifyEntityRef({ + name: templateName, + kind: 'template', + namespace, + }); const navigate = useNavigate(); const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef); const rootRoute = useRouteRef(rootRouteRef); - const { schema, loading, error } = useTemplateParameterSchema(templateName); + const { schema, loading, error } = useTemplateParameterSchema(templateRef); const [formState, setFormState] = useState>(() => { const query = qs.parse(window.location.search, { ignoreQueryPrefix: true, @@ -78,11 +83,7 @@ export const TemplatePage = ({ const handleCreate = async () => { const { taskId } = await scaffolderApi.scaffold({ - templateRef: stringifyEntityRef({ - name: templateName, - kind: 'template', - namespace: 'default', - }), + templateRef, values: formState, secrets: secretsContext?.secrets, }); diff --git a/plugins/scaffolder/src/next/Router/Router.test.tsx b/plugins/scaffolder/src/next/Router/Router.test.tsx index 5094c786e0..1aaca1a94b 100644 --- a/plugins/scaffolder/src/next/Router/Router.test.tsx +++ b/plugins/scaffolder/src/next/Router/Router.test.tsx @@ -47,7 +47,9 @@ describe('Router', () => { describe('/templates/:templateName', () => { it('should render the TemplateWizard page', async () => { - await renderInTestApp(, { routeEntries: ['/templates/foo'] }); + await renderInTestApp(, { + routeEntries: ['/templates/default/foo'], + }); expect(TemplateWizardPage).toHaveBeenCalled(); }); @@ -67,7 +69,7 @@ describe('Router', () => { , - { routeEntries: ['/templates/foo'] }, + { routeEntries: ['/templates/default/foo'] }, ); const mock = TemplateWizardPage as jest.Mock; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.test.tsx index 45f4dc6317..44e3a0bbbf 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.test.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.test.tsx @@ -233,7 +233,7 @@ describe('TemplateCard', () => { expect(getByRole('button', { name: 'Choose' })).toBeInTheDocument(); expect(getByRole('button', { name: 'Choose' })).toHaveAttribute( 'href', - '/templates/bob', + '/templates/default/bob', ); }); }); diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.tsx index 0e67ecd82a..874c20a9ac 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.tsx @@ -26,7 +26,11 @@ import { } from '@material-ui/core'; import { CardHeader } from './CardHeader'; import { MarkdownContent, UserIcon, Button } from '@backstage/core-components'; -import { RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { + parseEntityRef, + RELATION_OWNED_BY, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { EntityRefLinks, getEntityRelations, @@ -91,7 +95,8 @@ export const TemplateCard = (props: TemplateCardProps) => { const styles = useStyles(); const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY); const templateRoute = useRouteRef(selectedTemplateRouteRef); - const href = templateRoute({ templateName: template.metadata.name }); + const { name, namespace } = parseEntityRef(stringifyEntityRef(template)); + const href = templateRoute({ templateName: name, namespace: namespace }); return ( diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index be0d69135a..dbc54bb22c 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -28,10 +28,16 @@ export const rootRouteRef = createRouteRef({ id: 'scaffolder', }); +export const legacySelectedTemplateRouteRef = createSubRouteRef({ + id: 'scaffolder/legacy/selected-template', + parent: rootRouteRef, + path: '/templates/:templateName', +}); + export const selectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/selected-template', parent: rootRouteRef, - path: '/templates/:templateName', + path: '/templates/:namespace/:templateName', }); export const scaffolderTaskRouteRef = createSubRouteRef({ From f93af969cd9c04dac54061cc1bc12e2ebeb488db Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 9 Jun 2022 16:15:13 +0200 Subject: [PATCH 2/4] chore: tidying up a little bit and added changeset Signed-off-by: blam --- .changeset/tender-carpets-cry.md | 6 ++++++ plugins/scaffolder/src/components/Router.tsx | 14 ++++++++++++-- plugins/scaffolder/src/routes.ts | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .changeset/tender-carpets-cry.md diff --git a/.changeset/tender-carpets-cry.md b/.changeset/tender-carpets-cry.md new file mode 100644 index 0000000000..a3d275fe33 --- /dev/null +++ b/.changeset/tender-carpets-cry.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': minor +'@backstage/plugin-scaffolder-backend': minor +--- + +Added the ability to support running of templates that are not in the `default` namespace diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 172a21234a..60dc5ed474 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ComponentType } from 'react'; +import React, { ComponentType, useEffect } from 'react'; import { Routes, Route, useOutlet, Navigate, useParams } from 'react-router'; import { Entity } from '@backstage/catalog-model'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; @@ -101,10 +101,20 @@ export const Router = (props: RouterProps) => { ), ), ]; - + /** + * This component can be deleted once the older routes have been deprecated. + */ const RedirectingComponent = () => { const { templateName } = useParams(); const newLink = useRouteRef(selectedTemplateRouteRef); + // eslint-disable-next-line no-console + useEffect( + () => + console.warn( + 'The route /template/:templateName is deprecated, please use the new /template/:namespace/:templateName route instead', + ), + [], + ); return ; }; diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index dbc54bb22c..8d6875e351 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -28,6 +28,9 @@ export const rootRouteRef = createRouteRef({ id: 'scaffolder', }); +/** + * @deprecated This is the old template route, can be deleted before next major release + */ export const legacySelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/legacy/selected-template', parent: rootRouteRef, From 0a8b21216535dd0a7e42c1dca6277c81ec387f44 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 9 Jun 2022 16:22:08 +0200 Subject: [PATCH 3/4] chore: wrong line Signed-off-by: blam --- plugins/scaffolder/src/components/Router.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 60dc5ed474..ac35c4dcd7 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -107,9 +107,9 @@ export const Router = (props: RouterProps) => { const RedirectingComponent = () => { const { templateName } = useParams(); const newLink = useRouteRef(selectedTemplateRouteRef); - // eslint-disable-next-line no-console useEffect( () => + // eslint-disable-next-line no-console console.warn( 'The route /template/:templateName is deprecated, please use the new /template/:namespace/:templateName route instead', ), From 216172b0153120ad67c0e03c3b1c7fea39177f66 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 9 Jun 2022 16:41:54 +0200 Subject: [PATCH 4/4] chore: fix typescript error Signed-off-by: blam --- plugins/scaffolder-backend/src/service/helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index 13197861a8..3412fec2d3 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -21,7 +21,6 @@ import { parseLocationRef, ANNOTATION_SOURCE_LOCATION, CompoundEntityRef, - DEFAULT_NAMESPACE, stringifyEntityRef, } from '@backstage/catalog-model'; import { Config } from '@backstage/config';