From 65d7020f5cc4948e736eb09086aaadc6234304c3 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Tue, 4 Feb 2025 09:35:08 +0200 Subject: [PATCH 1/2] feat(scaffolder): use template title for header using the entity presentation api, use the template title for scaffolder ongoing page header. closes #28678 Signed-off-by: Hellgren Heikki --- .changeset/yellow-rules-vanish.md | 5 +++++ .../OngoingTask/OngoingTask.test.tsx | 6 +++++- .../components/OngoingTask/OngoingTask.tsx | 19 ++++++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 .changeset/yellow-rules-vanish.md diff --git a/.changeset/yellow-rules-vanish.md b/.changeset/yellow-rules-vanish.md new file mode 100644 index 0000000000..23fff893f6 --- /dev/null +++ b/.changeset/yellow-rules-vanish.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Use template title for ongoing task page header diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx index d62ef6f95e..371757852f 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx @@ -17,9 +17,9 @@ import { OngoingTask } from './OngoingTask'; import React from 'react'; import { + mockApis, renderInTestApp, TestApiProvider, - mockApis, } from '@backstage/test-utils'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { act, fireEvent, waitFor, within } from '@testing-library/react'; @@ -30,6 +30,7 @@ import { import { rootRouteRef } from '../../routes'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { SWRConfig } from 'swr'; +import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -59,6 +60,8 @@ describe('OngoingTask', () => { getTask: jest.fn().mockImplementation(async () => {}), }; + const mockEntityPresentationApi = {}; + beforeEach(async () => { jest.clearAllMocks(); }); @@ -71,6 +74,7 @@ describe('OngoingTask', () => { apis={[ [scaffolderApiRef, mockScaffolderApi], [permissionApiRef, permissionApi || mockApis.permission()], + [entityPresentationApiRef, mockEntityPresentationApi], ]} > diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 97047d00c9..9fff6b51ad 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -21,8 +21,8 @@ import Button from '@material-ui/core/Button'; import Paper from '@material-ui/core/Paper'; import { makeStyles } from '@material-ui/core/styles'; import { - ScaffolderTaskOutput, scaffolderApiRef, + ScaffolderTaskOutput, useTaskEventStream, } from '@backstage/plugin-scaffolder-react'; import { selectedTemplateRouteRef } from '../../routes'; @@ -38,11 +38,12 @@ import { useAsync } from '@react-hookz/web'; import { usePermission } from '@backstage/plugin-permission-react'; import { taskCancelPermission, - taskReadPermission, taskCreatePermission, + taskReadPermission, } from '@backstage/plugin-scaffolder-common/alpha'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../translation'; +import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -79,7 +80,9 @@ export const OngoingTask = (props: { const navigate = useNavigate(); const analytics = useAnalytics(); const scaffolderApi = useApi(scaffolderApiRef); + const entityPresentationApi = useApi(entityPresentationApiRef); const taskStream = useTaskEventStream(taskId!); + const [templateName, setTemplateName] = useState(); const classes = useStyles(); const steps = useMemo( () => @@ -123,6 +126,15 @@ export const OngoingTask = (props: { } }, [taskStream.error, taskStream.completed]); + useEffect(() => { + const templateEntityRef = taskStream.task?.spec.templateInfo?.entityRef; + if (!templateEntityRef) { + return; + } + const presentation = entityPresentationApi.forEntity(templateEntityRef); + setTemplateName(presentation.snapshot.primaryTitle); + }, [entityPresentationApi, taskStream.task?.spec.templateInfo?.entityRef]); + const activeStep = useMemo(() => { for (let i = steps.length - 1; i >= 0; i--) { if (steps[i].status !== 'open') { @@ -184,9 +196,6 @@ export const OngoingTask = (props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; - const templateName = - taskStream.task?.spec.templateInfo?.entity?.metadata.name || ''; - const cancelEnabled = !(taskStream.cancelled || taskStream.completed); return ( From 02941e9c6efaf8a9d14417a4c78dcc1d91c01139 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Tue, 4 Feb 2025 13:27:43 +0200 Subject: [PATCH 2/2] test(scaffolder): add ongoing task title rendering test Signed-off-by: Hellgren Heikki --- .../OngoingTask/OngoingTask.test.tsx | 17 +++++++++++++++-- .../src/components/OngoingTask/OngoingTask.tsx | 18 ++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx index 371757852f..7a04854c6f 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx @@ -48,7 +48,10 @@ jest.mock('@backstage/plugin-scaffolder-react', () => ({ task: { spec: { steps: [], - templateInfo: { entity: { metadata: { name: 'my-template' } } }, + templateInfo: { + entityRef: 'template:default/my-template', + entity: { metadata: { name: 'my-template' } }, + }, }, }, }), @@ -60,7 +63,11 @@ describe('OngoingTask', () => { getTask: jest.fn().mockImplementation(async () => {}), }; - const mockEntityPresentationApi = {}; + const mockEntityPresentationApi = { + forEntity: jest.fn().mockReturnValue({ + promise: new Promise(resolve => resolve({ primaryTitle: 'My template' })), + }), + }; beforeEach(async () => { jest.clearAllMocks(); @@ -83,6 +90,12 @@ describe('OngoingTask', () => { { mountedRoutes: { '/': rootRouteRef } }, ); }; + + it('should render title', async () => { + const rendered = await render(); + expect(rendered.getByText('My template')).toBeInTheDocument(); + }); + it('should trigger cancel api on "Cancel" click in context menu', async () => { const rendered = await render(); const cancelOptionLabel = 'Cancel'; diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 9fff6b51ad..06620e7280 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -44,6 +44,7 @@ import { import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../translation'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; +import { default as reactUseAsync } from 'react-use/esm/useAsync'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -82,7 +83,6 @@ export const OngoingTask = (props: { const scaffolderApi = useApi(scaffolderApiRef); const entityPresentationApi = useApi(entityPresentationApiRef); const taskStream = useTaskEventStream(taskId!); - const [templateName, setTemplateName] = useState(); const classes = useStyles(); const steps = useMemo( () => @@ -126,13 +126,12 @@ export const OngoingTask = (props: { } }, [taskStream.error, taskStream.completed]); - useEffect(() => { + const { value: presentation } = reactUseAsync(async () => { const templateEntityRef = taskStream.task?.spec.templateInfo?.entityRef; if (!templateEntityRef) { - return; + return undefined; } - const presentation = entityPresentationApi.forEntity(templateEntityRef); - setTemplateName(presentation.snapshot.primaryTitle); + return entityPresentationApi.forEntity(templateEntityRef).promise; }, [entityPresentationApi, taskStream.task?.spec.templateInfo?.entityRef]); const activeStep = useMemo(() => { @@ -202,13 +201,16 @@ export const OngoingTask = (props: {
- {t('ongoingTask.title')} {templateName} + {t('ongoingTask.title')}{' '} + {presentation ? presentation.primaryTitle : ''} } subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })}