diff --git a/plugins/scaffolder-backend/sample-templates/all-templates.yaml b/plugins/scaffolder-backend/sample-templates/all-templates.yaml
index 6637c9479b..54a3fb213e 100644
--- a/plugins/scaffolder-backend/sample-templates/all-templates.yaml
+++ b/plugins/scaffolder-backend/sample-templates/all-templates.yaml
@@ -3,6 +3,8 @@ kind: Location
metadata:
name: example-templates
description: A collection of all Backstage example templates
+# annotations:
+# backstage.io/edit-url: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/sample-templates/all-templates.yaml
spec:
targets:
- ./remote-templates.yaml
diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts
index 8bfbd6b915..babe93ad13 100644
--- a/plugins/scaffolder-backend/src/service/router.test.ts
+++ b/plugins/scaffolder-backend/src/service/router.test.ts
@@ -107,6 +107,7 @@ describe('createRouter', () => {
title: 'Create React App Template',
annotations: {
'backstage.io/managed-by-location': 'url:https://dev.azure.com',
+ 'backstage.io/edit-url': 'url:EDIT_URL',
},
},
spec: {
@@ -878,6 +879,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
expect(response.body).toEqual({
title: 'Create React App Template',
description: 'Create a new CRA website project',
+ editUrl: 'url:EDIT_URL',
steps: [
{
title: 'Please enter the following information',
@@ -932,6 +934,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
expect(response.body).toEqual({
title: 'Create React App Template',
description: 'Create a new CRA website project',
+ editUrl: 'url:EDIT_URL',
steps: [],
});
});
@@ -963,6 +966,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
expect(response.body).toEqual({
title: 'Create React App Template',
description: 'Create a new CRA website project',
+ editUrl: 'url:EDIT_URL',
steps: [
{
title: 'Please enter the following information',
@@ -983,6 +987,56 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
],
});
});
+ it('should not return editUrl', async () => {
+ jest
+ .spyOn(catalogClient, 'getEntityByRef')
+ .mockImplementationOnce(async () => {
+ const template = getMockTemplate();
+ delete template.metadata.annotations?.['backstage.io/edit-url'];
+ return template;
+ });
+ const response = await request(app)
+ .get(
+ '/v2/templates/default/Template/create-react-app-template/parameter-schema',
+ )
+ .send();
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({
+ title: 'Create React App Template',
+ description: 'Create a new CRA website project',
+ steps: [
+ {
+ title: 'Please enter the following information',
+ schema: {
+ required: ['requiredParameter1'],
+ type: 'object',
+ properties: {
+ requiredParameter1: {
+ description: 'Required parameter 1',
+ type: 'string',
+ },
+ },
+ },
+ },
+ {
+ title: 'Please enter the following information',
+ schema: {
+ type: 'object',
+ required: ['requiredParameter2'],
+ 'backstage:permissions': {
+ tags: ['parameters-tag'],
+ },
+ properties: {
+ requiredParameter2: {
+ type: 'string',
+ description: 'Required parameter 2',
+ },
+ },
+ },
+ },
+ ],
+ });
+ });
});
describe('POST /v2/tasks', () => {
diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts
index c0322af283..a70d6e9800 100644
--- a/plugins/scaffolder-backend/src/service/router.ts
+++ b/plugins/scaffolder-backend/src/service/router.ts
@@ -408,6 +408,7 @@ export async function createRouter(
res.json({
title: template.metadata.title ?? template.metadata.name,
...(presentation ? { presentation } : {}),
+ editUrl: template.metadata.annotations?.['backstage.io/edit-url'],
description: template.metadata.description,
'ui:options': template.metadata['ui:options'],
steps: parameters.map(schema => ({
diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx
index 81c5a697e0..e431e66ca2 100644
--- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx
+++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx
@@ -20,7 +20,7 @@ import {
renderInTestApp,
TestApiRegistry,
} from '@backstage/test-utils';
-import { act, fireEvent } from '@testing-library/react';
+import { act, fireEvent, screen } from '@testing-library/react';
import React from 'react';
import { Workflow } from './Workflow';
import { analyticsApiRef } from '@backstage/core-plugin-api';
@@ -75,7 +75,7 @@ describe('', () => {
title: 'React JSON Schema Form Test',
});
- const { getByRole, getAllByRole, getByText } = await renderInTestApp(
+ const { getByRole, getByText } = await renderInTestApp(
', () => {
).toBeDefined();
await act(async () => {
- fireEvent.click(getAllByRole('button')[1] as HTMLButtonElement);
+ fireEvent.click(getByText('Make') as HTMLButtonElement);
});
expect(onCreate).toHaveBeenCalledWith({
@@ -141,4 +141,72 @@ describe('', () => {
age: '53',
});
});
+ it('renders "edit template" button', async () => {
+ const onCreate = jest.fn();
+ const onError = jest.fn();
+ const expectedUrl = '/EDIT_URL';
+ scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({
+ editUrl: expectedUrl,
+ steps: [
+ {
+ title: 'Step 1',
+ schema: {},
+ },
+ ],
+ title: 'React JSON Schema Form Test',
+ });
+ await renderInTestApp(
+
+
+ ,
+ );
+ const editLink = screen.getByTitle('Edit Template').closest('a');
+ expect(editLink).toHaveAttribute('href', expectedUrl);
+ });
+ it('renders disable "edit template" button', async () => {
+ const onCreate = jest.fn();
+ const onError = jest.fn();
+ scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({
+ steps: [
+ {
+ title: 'Step 1',
+ schema: {},
+ },
+ ],
+ editUrl: undefined,
+ title: 'React JSON Schema Form Test',
+ });
+ await renderInTestApp(
+
+
+ ,
+ );
+ const editLink = screen.getByTitle('Edit Template');
+ expect(editLink).toBeVisible();
+ expect(editLink).toHaveAttribute('href', '/');
+ });
});
diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx
index c6030930c5..660f115064 100644
--- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx
+++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx
@@ -20,6 +20,7 @@ import {
InfoCard,
MarkdownContent,
Progress,
+ Link,
} from '@backstage/core-components';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { makeStyles } from '@material-ui/core';
@@ -29,6 +30,8 @@ import { Stepper, type StepperProps } from '../Stepper/Stepper';
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
import { useFilteredSchemaProperties } from '../../hooks/useFilteredSchemaProperties';
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
+import IconButton from '@material-ui/core/IconButton';
+import EditIcon from '@material-ui/icons/Edit';
const useStyles = makeStyles({
markdown: {
@@ -93,13 +96,23 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
if (error) {
return props.onError(error);
}
-
return (
{loading && }
{sortedManifest && (
+
+
+ }
subheader={