From 3dabebb5fa35c43d18de7cc9ba343f93642d7d03 Mon Sep 17 00:00:00 2001 From: James Turley Date: Mon, 15 Mar 2021 13:11:05 +0000 Subject: [PATCH 1/9] Add links to TaskOutput type Signed-off-by: James Turley Signed-off-by: James Turley --- .../scaffolder/src/components/hooks/useEventStream.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 210fd8cb9b..958c3d192e 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -19,6 +19,11 @@ import { scaffolderApiRef, LogEvent } from '../../api'; import { ScaffolderTask, Status } from '../../types'; import { Subscription, useApi } from '@backstage/core'; +type OutputLink = { + title: string; + url: string; +}; + type Step = { id: string; status: Status; @@ -26,7 +31,10 @@ type Step = { startedAt?: string; }; -type TaskOutput = { entityRef?: string } & { [key in string]: string }; +type TaskOutput = { + entityRef?: string; + links?: OutputLink[]; +} & { [key in string]: string }; export type TaskStream = { loading: boolean; From b96d9c5677dcbcf275b623ac89de1aab4d71c0a0 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 16 Mar 2021 10:45:48 +0000 Subject: [PATCH 2/9] Move IconLink from catalog to core To reuse in scaffolder. Signed-off-by: James Turley Signed-off-by: James Turley --- .../components/IconLink}/IconLink.test.tsx | 0 .../src/components/IconLink}/IconLink.tsx | 28 ++++++++++--------- .../core/src/components/IconLink/index.ts | 17 +++++++++++ packages/core/src/components/index.ts | 1 + .../EntityLinksCard/LinksGridList.tsx | 11 ++++++-- 5 files changed, 41 insertions(+), 16 deletions(-) rename {plugins/catalog/src/components/EntityLinksCard => packages/core/src/components/IconLink}/IconLink.test.tsx (100%) rename {plugins/catalog/src/components/EntityLinksCard => packages/core/src/components/IconLink}/IconLink.tsx (74%) create mode 100644 packages/core/src/components/IconLink/index.ts diff --git a/plugins/catalog/src/components/EntityLinksCard/IconLink.test.tsx b/packages/core/src/components/IconLink/IconLink.test.tsx similarity index 100% rename from plugins/catalog/src/components/EntityLinksCard/IconLink.test.tsx rename to packages/core/src/components/IconLink/IconLink.test.tsx diff --git a/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx b/packages/core/src/components/IconLink/IconLink.tsx similarity index 74% rename from plugins/catalog/src/components/EntityLinksCard/IconLink.tsx rename to packages/core/src/components/IconLink/IconLink.tsx index 32ea085889..c2f0a5c5da 100644 --- a/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx +++ b/packages/core/src/components/IconLink/IconLink.tsx @@ -14,10 +14,12 @@ * limitations under the License. */ -import { Link, IconComponent } from '@backstage/core'; -import { Grid, makeStyles, Typography } from '@material-ui/core'; -import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; +import { IconComponent } from '@backstage/core-api'; +import { Grid, LinkProps, makeStyles, Typography } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; +import { omit } from 'lodash'; +import { Link } from '../Link'; const useStyles = makeStyles({ svgIcon: { @@ -30,15 +32,15 @@ const useStyles = makeStyles({ }, }); -export const IconLink = ({ - href, - text, - Icon, -}: { - href: string; - text?: string; - Icon?: IconComponent; -}) => { +export const IconLink = ( + props: { + href: string; + text?: string; + Icon?: IconComponent; + } & LinkProps, +) => { + const { href, text, Icon, ...linkProps } = props; + const classes = useStyles(); return ( @@ -49,7 +51,7 @@ export const IconLink = ({ - + {text || href} diff --git a/packages/core/src/components/IconLink/index.ts b/packages/core/src/components/IconLink/index.ts new file mode 100644 index 0000000000..b650b07279 --- /dev/null +++ b/packages/core/src/components/IconLink/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { IconLink } from './IconLink'; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index c8797f576b..aec8cc9a64 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -26,6 +26,7 @@ export * from './ResponseErrorPanel'; export * from './FeatureDiscovery'; export * from './HeaderIconLinkRow'; export * from './HorizontalScrollGrid'; +export * from './IconLink'; export * from './Lifecycle'; export * from './Link'; export * from './MarkdownContent'; diff --git a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx index 9ef77a4bbf..a2793f9aa4 100644 --- a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx @@ -14,10 +14,9 @@ * limitations under the License. */ -import { IconComponent } from '@backstage/core'; +import { IconComponent, IconLink } from '@backstage/core'; import { GridList, GridListTile } from '@material-ui/core'; import React from 'react'; -import { IconLink } from './IconLink'; import { ColumnBreakpoints } from './types'; import { useDynamicColumns } from './useDynamicColumns'; @@ -39,7 +38,13 @@ export const LinksGridList = ({ items, cols = undefined }: Props) => { {items.map(({ text, href, Icon }, i) => ( - + ))} From baee14e5d624066eb0761b30c5d70ae95b035d93 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 16 Mar 2021 17:53:29 +0000 Subject: [PATCH 3/9] Add component for multiple links Signed-off-by: James Turley Signed-off-by: James Turley --- .../schema/kinds/Template.v1beta2.schema.json | 30 ++++++++ packages/core-api/src/icons/icons.tsx | 3 + .../src/components/TaskPage/TaskPage.tsx | 51 +++----------- .../src/components/TaskPage/TaskPageLinks.tsx | 69 +++++++++++++++++++ .../src/components/hooks/useEventStream.ts | 12 +--- plugins/scaffolder/src/types.ts | 13 ++++ 6 files changed, 126 insertions(+), 52 deletions(-) create mode 100644 plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx diff --git a/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json b/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json index bd9999f8bd..ba3ccb9d1d 100644 --- a/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json +++ b/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json @@ -135,6 +135,36 @@ "output": { "type": "object", "description": "A templated object describing the outputs of the scaffolding task.", + "properties": { + "links": { + "type": "array", + "description": "A list of external hyperlinks, typically pointing to resources created or updated by the template", + "items": { + "type": "object", + "required": ["url"], + "properties": { + "url": { + "type": "string", + "description": "A url in a standard uri format.", + "examples": ["https://github.com/my-org/my-new-repo"], + "minLength": 1 + }, + "title": { + "type": "string", + "description": "A user friendly display name for the link.", + "examples": ["View new repo"], + "minLength": 1 + }, + "icon": { + "type": "string", + "description": "A key representing a visual icon to be displayed in the UI.", + "examples": ["dashboard"], + "minLength": 1 + } + } + } + } + }, "additionalProperties": { "type": "string" } diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx index e638131a28..fd3d0dcdab 100644 --- a/packages/core-api/src/icons/icons.tsx +++ b/packages/core-api/src/icons/icons.tsx @@ -15,6 +15,7 @@ */ import { SvgIconProps } from '@material-ui/core'; +import MuiMenuBookIcon from '@material-ui/icons/MenuBook'; import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage'; import MuiChatIcon from '@material-ui/icons/Chat'; import MuiDashboardIcon from '@material-ui/icons/Dashboard'; @@ -32,6 +33,8 @@ import { IconComponent, IconComponentMap, SystemIconKey } from './types'; export const defaultSystemIcons: IconComponentMap = { brokenImage: MuiBrokenImageIcon, + // To be confirmed: see https://github.com/backstage/backstage/issues/4970 + catalog: MuiMenuBookIcon, chat: MuiChatIcon, dashboard: MuiDashboardIcon, email: MuiEmailIcon, diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 96a857dbb5..cc1149ee61 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -22,28 +22,24 @@ import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import { generatePath, useParams } from 'react-router'; +import { useParams } from 'react-router'; import { useTaskEventStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; -import { Link } from 'react-router-dom'; import { - Box, - Button, CircularProgress, Paper, StepButton, StepIconProps, } from '@material-ui/core'; -import { Status } from '../../types'; +import { Status, TaskOutput } from '../../types'; import { DateTime, Interval } from 'luxon'; import { useInterval } from 'react-use'; import Check from '@material-ui/icons/Check'; import Cancel from '@material-ui/icons/Cancel'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; -import { entityRoute } from '@backstage/plugin-catalog-react'; -import { parseEntityName } from '@backstage/catalog-model'; import classNames from 'classnames'; import { BackstageTheme } from '@backstage/theme'; +import { TaskPageLinks } from './TaskPageLinks'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -211,6 +207,9 @@ const TaskLogger = memo(({ log }: { log: string }) => { ); }); +const hasLinks = ({ entityRef, remoteUrl, links = [] }: TaskOutput): boolean => + !!(entityRef || remoteUrl || links.length > 0); + export const TaskPage = () => { const [userSelectedStepId, setUserSelectedStepId] = useState< string | undefined @@ -261,8 +260,8 @@ export const TaskPage = () => { taskStream.loading === false && !taskStream.task; - const entityRef = taskStream.output?.entityRef; - const remoteUrl = taskStream.output?.remoteUrl; + const { output } = taskStream; + return (
{ currentStepId={currentStepId} onUserStepChange={setUserSelectedStepId} /> - {(entityRef || remoteUrl) && ( - - {entityRef && ( - - )} - {remoteUrl && ( - - )} - + {output && hasLinks(output) && ( + )} diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx new file mode 100644 index 0000000000..931482194e --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { parseEntityName } from '@backstage/catalog-model'; +import { IconComponent, IconKey, IconLink, useApp } from '@backstage/core'; +import { entityRoute } from '@backstage/plugin-catalog-react'; +import { Box } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; +import { generatePath } from 'react-router'; +import { TaskOutput } from '../../types'; + +type TaskPageLinksProps = { + output: TaskOutput; +}; + +export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { + const { entityRef, remoteUrl } = output; + let { links = [] } = output; + const app = useApp(); + + const iconResolver = (key: IconKey | undefined): IconComponent => + key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; + + if (remoteUrl) { + links = [{ url: remoteUrl, title: 'Repo' }, ...links]; + } + + if (entityRef) { + links = [ + { + url: generatePath( + `/catalog/${entityRoute.path}`, + parseEntityName(entityRef), + ), + title: 'Open in catalog', + icon: 'catalog', + }, + ...links, + ]; + } + + return ( + + {links.map(({ url, title, icon }, i) => ( + + ))} + + ); +}; diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 958c3d192e..4d28fabd34 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -16,14 +16,9 @@ import { useImmerReducer } from 'use-immer'; import { useEffect } from 'react'; import { scaffolderApiRef, LogEvent } from '../../api'; -import { ScaffolderTask, Status } from '../../types'; +import { ScaffolderTask, Status, TaskOutput } from '../../types'; import { Subscription, useApi } from '@backstage/core'; -type OutputLink = { - title: string; - url: string; -}; - type Step = { id: string; status: Status; @@ -31,11 +26,6 @@ type Step = { startedAt?: string; }; -type TaskOutput = { - entityRef?: string; - links?: OutputLink[]; -} & { [key in string]: string }; - export type TaskStream = { loading: boolean; error?: Error; diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 6a2c2eede4..f96d103796 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -64,3 +64,16 @@ export type ListActionsResponse = Array<{ output?: JSONSchema; }; }>; + +type OutputLink = { + url: string; + title?: string; + icon?: string; +}; + +export type TaskOutput = { + entityRef?: string; + remoteUrl?: string; + links?: OutputLink[]; + [key: string]: any; +}; From b1ad7eae8a74aab1a0bb293c10aa248a0908fbea Mon Sep 17 00:00:00 2001 From: James Turley Date: Mon, 22 Mar 2021 15:27:13 +0000 Subject: [PATCH 4/9] Tests for TaskPageLinks Signed-off-by: James Turley Signed-off-by: James Turley --- .../core/src/components/IconLink/IconLink.tsx | 1 - .../TaskPage/TaskPageLinks.test.tsx | 76 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx diff --git a/packages/core/src/components/IconLink/IconLink.tsx b/packages/core/src/components/IconLink/IconLink.tsx index c2f0a5c5da..d7f9e5571b 100644 --- a/packages/core/src/components/IconLink/IconLink.tsx +++ b/packages/core/src/components/IconLink/IconLink.tsx @@ -18,7 +18,6 @@ import React from 'react'; import { IconComponent } from '@backstage/core-api'; import { Grid, LinkProps, makeStyles, Typography } from '@material-ui/core'; import LanguageIcon from '@material-ui/icons/Language'; -import { omit } from 'lodash'; import { Link } from '../Link'; const useStyles = makeStyles({ diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx new file mode 100644 index 0000000000..5ea170eae2 --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { TaskPageLinks } from './TaskPageLinks'; +import { renderInTestApp } from '@backstage/test-utils'; + +describe('TaskPageLinks', () => { + beforeEach(() => {}); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('renders the entityRef link', async () => { + const output = { entityRef: 'Component:default/my-app' }; + const { findByText } = await renderInTestApp( + , + ); + + const element = await findByText('Open in catalog'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute( + 'href', + '/catalog/default/Component/my-app', + ); + }); + + it('renders the remoteUrl link', async () => { + const output = { remoteUrl: 'https://remote.url' }; + const { findByText } = await renderInTestApp( + , + ); + + const element = await findByText('Repo'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute('href', 'https://remote.url'); + }); + + it('renders further links', async () => { + const output = { + links: [ + { url: 'https://first.url', title: 'Cool link 1' }, + { url: 'https://second.url', title: 'Cool link 2' }, + ], + }; + const { findByText } = await renderInTestApp( + , + ); + + let element = await findByText('Cool link 1'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute('href', 'https://first.url'); + + element = await findByText('Cool link 2'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute('href', 'https://second.url'); + }); +}); From 98dd5da71745c283687c0e9eac71fca414e11a7c Mon Sep 17 00:00:00 2001 From: James Turley Date: Mon, 22 Mar 2021 15:55:03 +0000 Subject: [PATCH 5/9] Changeset for task page links Signed-off-by: James Turley Signed-off-by: James Turley --- .changeset/sharp-rings-march.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/sharp-rings-march.md diff --git a/.changeset/sharp-rings-march.md b/.changeset/sharp-rings-march.md new file mode 100644 index 0000000000..81ea9aa9df --- /dev/null +++ b/.changeset/sharp-rings-march.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-model': patch +'@backstage/core': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-scaffolder': patch +--- + +Add support for multiple links to post-scaffold task summary page From 9fdbc1e2166fe4d08af814a5591408accd3074eb Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 6 Apr 2021 15:26:33 +0100 Subject: [PATCH 6/9] Deprecate remoteUrl Signed-off-by: James Turley --- plugins/scaffolder/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index f96d103796..233e1192e9 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -73,6 +73,7 @@ type OutputLink = { export type TaskOutput = { entityRef?: string; + /** @deprecated use the `links` property to link out to relevant resources */ remoteUrl?: string; links?: OutputLink[]; [key: string]: any; From 20ae6504c7b8099e612b243bc6f101fdef2411fa Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 6 Apr 2021 15:37:32 +0100 Subject: [PATCH 7/9] Duplicate IconLink instead of moving to core Signed-off-by: James Turley --- .../core/src/components/IconLink/index.ts | 17 ------ packages/core/src/components/index.ts | 1 - .../EntityLinksCard}/IconLink.test.tsx | 0 .../components/EntityLinksCard/IconLink.tsx | 58 +++++++++++++++++++ .../EntityLinksCard/LinksGridList.tsx | 11 +--- .../src/components/TaskPage/IconLink.test.tsx | 38 ++++++++++++ .../src/components/TaskPage}/IconLink.tsx | 3 +- .../src/components/TaskPage/TaskPageLinks.tsx | 3 +- 8 files changed, 102 insertions(+), 29 deletions(-) delete mode 100644 packages/core/src/components/IconLink/index.ts rename {packages/core/src/components/IconLink => plugins/catalog/src/components/EntityLinksCard}/IconLink.test.tsx (100%) create mode 100644 plugins/catalog/src/components/EntityLinksCard/IconLink.tsx create mode 100644 plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx rename {packages/core/src/components/IconLink => plugins/scaffolder/src/components/TaskPage}/IconLink.tsx (94%) diff --git a/packages/core/src/components/IconLink/index.ts b/packages/core/src/components/IconLink/index.ts deleted file mode 100644 index b650b07279..0000000000 --- a/packages/core/src/components/IconLink/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { IconLink } from './IconLink'; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index aec8cc9a64..c8797f576b 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -26,7 +26,6 @@ export * from './ResponseErrorPanel'; export * from './FeatureDiscovery'; export * from './HeaderIconLinkRow'; export * from './HorizontalScrollGrid'; -export * from './IconLink'; export * from './Lifecycle'; export * from './Link'; export * from './MarkdownContent'; diff --git a/packages/core/src/components/IconLink/IconLink.test.tsx b/plugins/catalog/src/components/EntityLinksCard/IconLink.test.tsx similarity index 100% rename from packages/core/src/components/IconLink/IconLink.test.tsx rename to plugins/catalog/src/components/EntityLinksCard/IconLink.test.tsx diff --git a/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx b/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx new file mode 100644 index 0000000000..32ea085889 --- /dev/null +++ b/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Link, IconComponent } from '@backstage/core'; +import { Grid, makeStyles, Typography } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; +import React from 'react'; + +const useStyles = makeStyles({ + svgIcon: { + display: 'inline-block', + '& svg': { + display: 'inline-block', + fontSize: 'inherit', + verticalAlign: 'baseline', + }, + }, +}); + +export const IconLink = ({ + href, + text, + Icon, +}: { + href: string; + text?: string; + Icon?: IconComponent; +}) => { + const classes = useStyles(); + + return ( + + + + {Icon ? : } + + + + + {text || href} + + + + ); +}; diff --git a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx index a2793f9aa4..9ef77a4bbf 100644 --- a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx @@ -14,9 +14,10 @@ * limitations under the License. */ -import { IconComponent, IconLink } from '@backstage/core'; +import { IconComponent } from '@backstage/core'; import { GridList, GridListTile } from '@material-ui/core'; import React from 'react'; +import { IconLink } from './IconLink'; import { ColumnBreakpoints } from './types'; import { useDynamicColumns } from './useDynamicColumns'; @@ -38,13 +39,7 @@ export const LinksGridList = ({ items, cols = undefined }: Props) => { {items.map(({ text, href, Icon }, i) => ( - + ))} diff --git a/plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx b/plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx new file mode 100644 index 0000000000..8caf37e70d --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { lightTheme } from '@backstage/theme'; +import { ThemeProvider } from '@material-ui/core'; +import CloudIcon from '@material-ui/icons/Cloud'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { IconLink } from './IconLink'; + +describe('IconLink', () => { + it('should render an icon link', () => { + const rendered = render( + + + , + ); + + expect(rendered.queryByText('I am Link')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/IconLink/IconLink.tsx b/plugins/scaffolder/src/components/TaskPage/IconLink.tsx similarity index 94% rename from packages/core/src/components/IconLink/IconLink.tsx rename to plugins/scaffolder/src/components/TaskPage/IconLink.tsx index d7f9e5571b..9bede214c6 100644 --- a/packages/core/src/components/IconLink/IconLink.tsx +++ b/plugins/scaffolder/src/components/TaskPage/IconLink.tsx @@ -15,10 +15,9 @@ */ import React from 'react'; -import { IconComponent } from '@backstage/core-api'; +import { IconComponent, Link } from '@backstage/core'; import { Grid, LinkProps, makeStyles, Typography } from '@material-ui/core'; import LanguageIcon from '@material-ui/icons/Language'; -import { Link } from '../Link'; const useStyles = makeStyles({ svgIcon: { diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx index 931482194e..4eed65e5bb 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx @@ -16,7 +16,8 @@ import React from 'react'; import { parseEntityName } from '@backstage/catalog-model'; -import { IconComponent, IconKey, IconLink, useApp } from '@backstage/core'; +import { IconComponent, IconKey, useApp } from '@backstage/core'; +import { IconLink } from './IconLink'; import { entityRoute } from '@backstage/plugin-catalog-react'; import { Box } from '@material-ui/core'; import LanguageIcon from '@material-ui/icons/Language'; From 04eae5a8db2489cec696a5def769ace8f577cf2a Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 6 Apr 2021 15:44:05 +0100 Subject: [PATCH 8/9] Use route ref hook for entity page link Signed-off-by: James Turley --- .../components/TaskPage/TaskPageLinks.test.tsx | 16 ++++++++++++++++ .../src/components/TaskPage/TaskPageLinks.tsx | 13 ++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx index 5ea170eae2..cd51488242 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { TaskPageLinks } from './TaskPageLinks'; import { renderInTestApp } from '@backstage/test-utils'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; describe('TaskPageLinks', () => { beforeEach(() => {}); @@ -29,6 +30,11 @@ describe('TaskPageLinks', () => { const output = { entityRef: 'Component:default/my-app' }; const { findByText } = await renderInTestApp( , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, ); const element = await findByText('Open in catalog'); @@ -44,6 +50,11 @@ describe('TaskPageLinks', () => { const output = { remoteUrl: 'https://remote.url' }; const { findByText } = await renderInTestApp( , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, ); const element = await findByText('Repo'); @@ -61,6 +72,11 @@ describe('TaskPageLinks', () => { }; const { findByText } = await renderInTestApp( , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, ); let element = await findByText('Cool link 1'); diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx index 4eed65e5bb..c0b55193c2 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx @@ -16,12 +16,11 @@ import React from 'react'; import { parseEntityName } from '@backstage/catalog-model'; -import { IconComponent, IconKey, useApp } from '@backstage/core'; +import { IconComponent, IconKey, useApp, useRouteRef } from '@backstage/core'; import { IconLink } from './IconLink'; -import { entityRoute } from '@backstage/plugin-catalog-react'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { Box } from '@material-ui/core'; import LanguageIcon from '@material-ui/icons/Language'; -import { generatePath } from 'react-router'; import { TaskOutput } from '../../types'; type TaskPageLinksProps = { @@ -32,6 +31,7 @@ export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { const { entityRef, remoteUrl } = output; let { links = [] } = output; const app = useApp(); + const entityRoute = useRouteRef(entityRouteRef); const iconResolver = (key: IconKey | undefined): IconComponent => key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; @@ -41,12 +41,11 @@ export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { } if (entityRef) { + const entityName = parseEntityName(entityRef); + const target = entityRoute(entityName); links = [ { - url: generatePath( - `/catalog/${entityRoute.path}`, - parseEntityName(entityRef), - ), + url: target, title: 'Open in catalog', icon: 'catalog', }, From 26526f1962b7db7b933220b578abca072e162073 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 6 Apr 2021 15:46:42 +0100 Subject: [PATCH 9/9] Clean up TaskOutput typedef Signed-off-by: James Turley --- plugins/scaffolder/src/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 233e1192e9..9ba480a441 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -76,5 +76,6 @@ export type TaskOutput = { /** @deprecated use the `links` property to link out to relevant resources */ remoteUrl?: string; links?: OutputLink[]; - [key: string]: any; +} & { + [key: string]: unknown; };