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: (