feat: added the ability to run namespaced templates
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user