diff --git a/.changeset/odd-singers-taste.md b/.changeset/odd-singers-taste.md new file mode 100644 index 0000000000..524de42255 --- /dev/null +++ b/.changeset/odd-singers-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index a6c613ded7..0b8107a216 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -136,6 +136,7 @@ const app = createApp({ }); bind(scaffolderPlugin.externalRoutes, { registerComponent: catalogImportPlugin.routes.importPage, + viewTechDoc: techdocsPlugin.routes.docRoot, }); bind(orgPlugin.externalRoutes, { catalogIndex: catalogPlugin.routes.catalogIndex, diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index e88d2861a7..ffa839740e 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -396,6 +396,14 @@ export const scaffolderPlugin: BackstagePlugin< }, { registerComponent: ExternalRouteRef; + viewTechDoc: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; }, {} >; diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index df46a119f2..9d1509e7a6 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -14,12 +14,24 @@ * limitations under the License. */ import { + DEFAULT_NAMESPACE, Entity, + EntityLink, parseEntityRef, RELATION_OWNED_BY, stringifyEntityRef, } from '@backstage/catalog-model'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { + Button, + ItemCardHeader, + MarkdownContent, +} from '@backstage/core-components'; +import { + IconComponent, + useApi, + useApp, + useRouteRef, +} from '@backstage/core-plugin-api'; import { ScmIntegrationIcon, scmIntegrationsApiRef, @@ -30,6 +42,7 @@ import { getEntityRelations, getEntitySourceLocation, } from '@backstage/plugin-catalog-react'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { BackstageTheme } from '@backstage/theme'; import { Box, @@ -45,16 +58,10 @@ import { Typography, useTheme, } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; import WarningIcon from '@material-ui/icons/Warning'; import React from 'react'; -import { selectedTemplateRouteRef } from '../../routes'; - -import { - Button, - ItemCardHeader, - MarkdownContent, -} from '@backstage/core-components'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { selectedTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; const useStyles = makeStyles(theme => ({ cardHeader: { @@ -69,7 +76,6 @@ const useStyles = makeStyles(theme => ({ display: '-webkit-box', '-webkit-line-clamp': 10, '-webkit-box-orient': 'vertical', - paddingBottom: '0.8em', }, label: { color: theme.palette.text.secondary, @@ -80,6 +86,14 @@ const useStyles = makeStyles(theme => ({ lineHeight: 1, paddingBottom: '0.2rem', }, + linksLabel: { + padding: '0 16px', + }, + description: { + '& p': { + margin: '0px', + }, + }, leftButton: { marginRight: 'auto', }, @@ -92,6 +106,8 @@ const useStyles = makeStyles(theme => ({ }, })); +const MuiIcon = ({ icon: Icon }: { icon: IconComponent }) => ; + const useDeprecationStyles = makeStyles(theme => ({ deprecationIcon: { position: 'absolute', @@ -115,6 +131,7 @@ type TemplateProps = { title: string; type: string; name: string; + links: EntityLink[]; }; const getTemplateCardProps = ( @@ -127,6 +144,7 @@ const getTemplateCardProps = ( type: template.spec.type ?? '', description: template.metadata.description ?? '-', tags: (template.metadata?.tags as string[]) ?? [], + links: template.metadata.links ?? [], }; }; @@ -155,6 +173,7 @@ const DeprecationWarning = () => { }; export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { + const app = useApp(); const backstageTheme = useTheme(); const templateRoute = useRouteRef(selectedTemplateRouteRef); const templateProps = getTemplateCardProps(template); @@ -170,6 +189,22 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { const { name, namespace } = parseEntityRef(stringifyEntityRef(template)); const href = templateRoute({ templateName: name, namespace }); + // TechDocs Link + const viewTechDoc = useRouteRef(viewTechDocRouteRef); + const viewTechDocsAnnotation = + template.metadata.annotations?.['backstage.io/techdocs-ref']; + const viewTechDocsLink = + !!viewTechDocsAnnotation && + !!viewTechDoc && + viewTechDoc({ + namespace: template.metadata.namespace || DEFAULT_NAMESPACE, + kind: template.kind, + name: template.metadata.name, + }); + + const iconResolver = (key?: string): IconComponent => + key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; + const scmIntegrationsApi = useApi(scmIntegrationsApiRef); const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi); @@ -184,12 +219,17 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { classes={{ root: classes.title }} /> - + Description - + @@ -198,7 +238,11 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { - + Tags {templateProps.tags?.map(tag => ( @@ -206,15 +250,47 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { ))} + + Links + - {sourceLocation && ( - - - - )} +
+ {sourceLocation && ( + + + + + + )} + {viewTechDocsLink && ( + + + + + + )} + {templateProps.links?.map((link, i) => ( + + + + + + ))} +