feat: added the ability to run namespaced templates

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-06-09 16:12:16 +02:00
parent 98941a4304
commit f780859eb0
9 changed files with 49 additions and 24 deletions
@@ -95,11 +95,6 @@ export async function findTemplate(options: {
}): Promise<TemplateEntityV1beta3> {
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`);
}
+12 -2
View File
@@ -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 <Navigate to={newLink({ namespace: 'default', templateName })} />;
};
return (
<Routes>
<Route
@@ -112,6 +119,9 @@ export const Router = (props: RouterProps) => {
/>
}
/>
<Route path={legacySelectedTemplateRouteRef.path}>
<RedirectingComponent />
</Route>
<Route
path={selectedTemplateRouteRef.path}
element={
@@ -299,12 +299,12 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => {
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),
})}`,
);
@@ -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);
@@ -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<Record<string, any>>(() => {
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,
});
@@ -47,7 +47,9 @@ describe('Router', () => {
describe('/templates/:templateName', () => {
it('should render the TemplateWizard page', async () => {
await renderInTestApp(<Router />, { routeEntries: ['/templates/foo'] });
await renderInTestApp(<Router />, {
routeEntries: ['/templates/default/foo'],
});
expect(TemplateWizardPage).toHaveBeenCalled();
});
@@ -67,7 +69,7 @@ describe('Router', () => {
<CustomFieldExtension />
</ScaffolderFieldExtensions>
</Router>,
{ routeEntries: ['/templates/foo'] },
{ routeEntries: ['/templates/default/foo'] },
);
const mock = TemplateWizardPage as jest.Mock;
@@ -233,7 +233,7 @@ describe('TemplateCard', () => {
expect(getByRole('button', { name: 'Choose' })).toBeInTheDocument();
expect(getByRole('button', { name: 'Choose' })).toHaveAttribute(
'href',
'/templates/bob',
'/templates/default/bob',
);
});
});
@@ -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 (
<Card>
+7 -1
View File
@@ -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({