diff --git a/.changeset/rotten-nails-impress.md b/.changeset/rotten-nails-impress.md new file mode 100644 index 0000000000..f7f56a0ada --- /dev/null +++ b/.changeset/rotten-nails-impress.md @@ -0,0 +1,7 @@ +--- +'@backstage/core': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-pagerduty': patch +--- + +Create AboutCard in core and use it in pagerduty and catalog plugin diff --git a/plugins/pagerduty/src/components/About/SubHeader.tsx b/packages/core/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx similarity index 73% rename from plugins/pagerduty/src/components/About/SubHeader.tsx rename to packages/core/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx index 5e4ea96fe0..a2f86d133b 100644 --- a/plugins/pagerduty/src/components/About/SubHeader.tsx +++ b/packages/core/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx @@ -14,8 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { VerticalIcon } from './VerticalIcon'; -import { SubHeaderLink } from '../types'; +import { IconLinkVertical, IconLinkVerticalProps } from './IconLinkVertical'; import { makeStyles } from '@material-ui/core'; const useStyles = makeStyles(theme => ({ @@ -29,21 +28,15 @@ const useStyles = makeStyles(theme => ({ })); type Props = { - links: SubHeaderLink[]; + links: IconLinkVerticalProps[]; }; -export const SubHeader = ({ links }: Props) => { +export const HeaderIconLinkRow = ({ links }: Props) => { const classes = useStyles(); return ( ); diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx similarity index 81% rename from plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx rename to packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx index dd267dde35..7a9078c3e4 100644 --- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx +++ b/packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx @@ -13,21 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import classnames from 'classnames'; import { makeStyles, Link } from '@material-ui/core'; import LinkIcon from '@material-ui/icons/Link'; -import { Link as RouterLink } from 'react-router-dom'; +import { Link as RouterLink } from '../Link'; export type IconLinkVerticalProps = { icon?: React.ReactNode; href?: string; disabled?: boolean; - title?: string; label: string; + action?: React.ReactNode; }; -const useIconStyles = makeStyles({ +const useIconStyles = makeStyles(theme => ({ link: { display: 'grid', justifyItems: 'center', @@ -43,12 +43,16 @@ const useIconStyles = makeStyles({ fontWeight: 600, letterSpacing: 1.2, }, -}); + linkStyle: { + color: theme.palette.secondary.main, + }, +})); export function IconLinkVertical({ icon = , href = '#', disabled = false, + action, ...props }: IconLinkVerticalProps) { const classes = useIconStyles(); @@ -58,7 +62,6 @@ export function IconLinkVertical({ {icon} @@ -67,12 +70,11 @@ export function IconLinkVertical({ ); } - // Absolute links should not be using RouterLink - if (href?.startsWith('//') || href?.includes('://')) { + if (action) { return ( - + {icon} - {props.label} + {action} ); } diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/index.ts b/packages/core/src/components/HeaderIconLinkRow/index.ts similarity index 91% rename from plugins/catalog/src/components/AboutCard/IconLinkVertical/index.ts rename to packages/core/src/components/HeaderIconLinkRow/index.ts index ddc146dbd8..82fb27cfad 100644 --- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/index.ts +++ b/packages/core/src/components/HeaderIconLinkRow/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { IconLinkVertical } from './IconLinkVertical'; +export { HeaderIconLinkRow } from './HeaderIconLinkRow'; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index a8ae0213c4..85ebba7a58 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -38,3 +38,4 @@ export * from './TrendLine'; export * from './WarningPanel'; export * from './EmptyState'; export * from './MarkdownContent'; +export * from './HeaderIconLinkRow'; diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 49653fe981..1549d87b8a 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -17,56 +17,27 @@ import { Entity, ENTITY_DEFAULT_NAMESPACE, - RELATION_OWNED_BY, RELATION_PROVIDES_API, - serializeEntityRef, } from '@backstage/catalog-model'; import { Card, CardContent, CardHeader, - Chip, Divider, - Grid, IconButton, makeStyles, - Typography, } from '@material-ui/core'; import ExtensionIcon from '@material-ui/icons/Extension'; import DocsIcon from '@material-ui/icons/Description'; import EditIcon from '@material-ui/icons/Edit'; import GitHubIcon from '@material-ui/icons/GitHub'; import React from 'react'; -import { IconLinkVertical } from './IconLinkVertical'; import { findLocationForEntityMeta } from '../../data/utils'; import { createEditLink, determineUrlType } from '../createEditLink'; +import { HeaderIconLinkRow } from '@backstage/core'; +import { AboutContent } from './AboutContent'; -const useStyles = makeStyles(theme => ({ - links: { - margin: theme.spacing(2, 0), - display: 'grid', - gridAutoFlow: 'column', - gridAutoColumns: 'min-content', - gridGap: theme.spacing(3), - }, - label: { - color: theme.palette.text.secondary, - textTransform: 'uppercase', - fontSize: '10px', - fontWeight: 'bold', - letterSpacing: 0.5, - overflow: 'hidden', - whiteSpace: 'nowrap', - }, - value: { - fontWeight: 'bold', - overflow: 'hidden', - lineHeight: '24px', - wordBreak: 'break-word', - }, - description: { - wordBreak: 'break-word', - }, +const useStyles = makeStyles({ gridItemCard: { display: 'flex', flexDirection: 'column', @@ -76,7 +47,7 @@ const useStyles = makeStyles(theme => ({ gridItemCardContent: { flex: 1, }, -})); +}); const iconMap: Record = { github: , @@ -114,6 +85,25 @@ export function AboutCard({ entity, variant }: AboutCardProps) { const codeLink = getCodeLinkInfo(entity); // TODO: Also support RELATION_CONSUMES_API here const hasApis = entity.relations?.some(r => r.type === RELATION_PROVIDES_API); + const viewInSource = { + label: 'View Source', + ...codeLink, + }; + const viewInTechDocs = { + label: 'View TechDocs', + disabled: !entity.metadata.annotations?.['backstage.io/techdocs-ref'], + icon: , + href: `/docs/${entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE}/${ + entity.kind + }/${entity.metadata.name}`, + }; + const viewApi = { + title: hasApis ? '' : 'No APIs available', + label: 'View API', + disabled: !hasApis, + icon: , + href: 'api', + }; return ( @@ -131,117 +121,15 @@ export function AboutCard({ entity, variant }: AboutCardProps) { } subheader={ - + } /> - - - - {entity?.metadata?.description || 'No description'} - - - r.type === RELATION_OWNED_BY) - .map(({ target: { kind, name, namespace } }) => - // TODO(Rugvip): we want to provide some utils for this - serializeEntityRef({ - kind, - name, - namespace: - namespace === ENTITY_DEFAULT_NAMESPACE - ? undefined - : namespace, - }), - ) - .join(', ')} - gridSizes={{ xs: 12, sm: 6, lg: 4 }} - /> - - - - {(entity?.metadata?.tags || []).map(t => ( - - ))} - - + ); } - -function AboutField({ - label, - value, - gridSizes, - children, -}: { - label: string; - value?: string; - gridSizes?: Record; - children?: React.ReactNode; -}) { - const classes = useStyles(); - - // Content is either children or a string prop `value` - const content = React.Children.count(children) ? ( - children - ) : ( - - {value || `unknown`} - - ); - return ( - - - {label} - - {content} - - ); -} diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx new file mode 100644 index 0000000000..b8d5d84316 --- /dev/null +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -0,0 +1,83 @@ +/* + * 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 { Grid, Typography, Chip, makeStyles } from '@material-ui/core'; +import { AboutField } from './AboutField'; +import { + Entity, + ENTITY_DEFAULT_NAMESPACE, + RELATION_OWNED_BY, + serializeEntityRef, +} from '@backstage/catalog-model'; + +const useStyles = makeStyles({ + description: { + wordBreak: 'break-word', + }, +}); + +type Props = { + entity: Entity; +}; + +export const AboutContent = ({ entity }: Props) => { + const classes = useStyles(); + return ( + + + + {entity?.metadata?.description || 'No description'} + + + r.type === RELATION_OWNED_BY) + .map(({ target: { kind, name, namespace } }) => + // TODO(Rugvip): we want to provide some utils for this + serializeEntityRef({ + kind, + name, + namespace: + namespace === ENTITY_DEFAULT_NAMESPACE ? undefined : namespace, + }), + ) + .join(', ')} + gridSizes={{ xs: 12, sm: 6, lg: 4 }} + /> + + + + {(entity?.metadata?.tags || []).map(t => ( + + ))} + + + ); +}; diff --git a/plugins/catalog/src/components/AboutCard/AboutField.tsx b/plugins/catalog/src/components/AboutCard/AboutField.tsx new file mode 100644 index 0000000000..fdcb3ace1b --- /dev/null +++ b/plugins/catalog/src/components/AboutCard/AboutField.tsx @@ -0,0 +1,64 @@ +/* + * 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 { makeStyles, Typography, Grid } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + value: { + fontWeight: 'bold', + overflow: 'hidden', + lineHeight: '24px', + wordBreak: 'break-word', + }, + label: { + color: theme.palette.text.secondary, + textTransform: 'uppercase', + fontSize: '10px', + fontWeight: 'bold', + letterSpacing: 0.5, + overflow: 'hidden', + whiteSpace: 'nowrap', + }, +})); + +type Props = { + label: string; + value?: string; + gridSizes?: Record; + children?: React.ReactNode; +}; + +export const AboutField = ({ label, value, gridSizes, children }: Props) => { + const classes = useStyles(); + + // Content is either children or a string prop `value` + const content = React.Children.count(children) ? ( + children + ) : ( + + {value || `unknown`} + + ); + return ( + + + {label} + + {content} + + ); +}; diff --git a/plugins/catalog/src/components/AboutCard/index.ts b/plugins/catalog/src/components/AboutCard/index.ts index 0491d467f8..93765ff942 100644 --- a/plugins/catalog/src/components/AboutCard/index.ts +++ b/plugins/catalog/src/components/AboutCard/index.ts @@ -15,4 +15,3 @@ */ export { AboutCard } from './AboutCard'; -export { IconLinkVertical } from './IconLinkVertical'; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 5949209128..606ad9028d 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -15,7 +15,7 @@ */ export * from '@backstage/catalog-client'; -export { AboutCard, IconLinkVertical } from './components/AboutCard'; +export { AboutCard } from './components/AboutCard'; export { EntityPageLayout } from './components/EntityPageLayout'; export { Router } from './components/Router'; export { useEntityCompoundName } from './components/useEntityCompoundName'; diff --git a/plugins/pagerduty/src/components/About/AboutCard.tsx b/plugins/pagerduty/src/components/About/AboutCard.tsx deleted file mode 100644 index 7d5419908a..0000000000 --- a/plugins/pagerduty/src/components/About/AboutCard.tsx +++ /dev/null @@ -1,33 +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. - */ -import React from 'react'; -import { Card, CardContent, CardHeader, Divider } from '@material-ui/core'; -import { SubHeader } from './SubHeader'; -import { SubHeaderLink } from '../types'; - -type Props = { - title: string; - links: SubHeaderLink[]; - content: React.ReactNode; -}; - -export const AboutCard = ({ title, links, content }: Props) => ( - - } /> - - {content} - -); diff --git a/plugins/pagerduty/src/components/About/VerticalIcon.tsx b/plugins/pagerduty/src/components/About/VerticalIcon.tsx deleted file mode 100644 index 5463372ac0..0000000000 --- a/plugins/pagerduty/src/components/About/VerticalIcon.tsx +++ /dev/null @@ -1,85 +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. - */ -import React from 'react'; -import classnames from 'classnames'; -import { makeStyles, Link } from '@material-ui/core'; -import LinkIcon from '@material-ui/icons/Link'; -import { Link as RouterLink } from 'react-router-dom'; - -export type VerticalIconProps = { - icon?: React.ReactNode; - href?: string; - title?: string; - label: string; - action?: React.ReactNode; -}; - -const useIconStyles = makeStyles(theme => ({ - link: { - display: 'grid', - justifyItems: 'center', - gridGap: 4, - textAlign: 'center', - }, - label: { - fontSize: '0.7rem', - textTransform: 'uppercase', - fontWeight: 600, - letterSpacing: 1.2, - }, - linkStyle: { - color: theme.palette.secondary.main, - }, -})); - -export function VerticalIcon({ - icon = , - href = '#', - action, - ...props -}: VerticalIconProps) { - const classes = useIconStyles(); - - if (action) { - return ( - - {icon} - {action} - - ); - } - - // Absolute links should not be using RouterLink - if (href?.startsWith('//') || href?.includes('://')) { - return ( - - {icon} - {props.label} - - ); - } - - return ( - - {icon} - {props.label} - - ); -} diff --git a/plugins/pagerduty/src/components/About/index.ts b/plugins/pagerduty/src/components/About/index.ts deleted file mode 100644 index 4e495daa54..0000000000 --- a/plugins/pagerduty/src/components/About/index.ts +++ /dev/null @@ -1,19 +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 { AboutCard } from './AboutCard'; -// export { SubHeader } from './SubHeader'; -// export { VerticalIcon } from './VerticalIcon'; diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx index 8fd20f37e8..5fb2cd7f4a 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -14,9 +14,16 @@ * limitations under the License. */ import React, { useState, useCallback } from 'react'; -import { useApi, Progress } from '@backstage/core'; +import { useApi, Progress, HeaderIconLinkRow } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { Button, makeStyles } from '@material-ui/core'; +import { + Button, + makeStyles, + Card, + CardHeader, + Divider, + CardContent, +} from '@material-ui/core'; import { Incidents } from './Incident'; import { EscalationPolicy } from './Escalation'; import { useAsync } from 'react-use'; @@ -26,7 +33,6 @@ import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import { TriggerDialog } from './TriggerDialog'; import { MissingTokenError } from './Errors/MissingTokenError'; import WebIcon from '@material-ui/icons/Web'; -import { AboutCard } from './About/AboutCard'; const useStyles = makeStyles({ triggerAlarm: { @@ -98,13 +104,13 @@ export const PagerDutyCard = ({ entity }: Props) => { } const serviceLink = { - title: 'Service Directory', + label: 'Service Directory', href: service!.url, icon: , }; const triggerLink = { - title: 'Create Incident', + label: 'Create Incident', action: (