feat(techdocs) allow referencing other entities for techdocs

Signed-off-by: jrwpatterson <jrwpatterson@gmail.com>
This commit is contained in:
jrwpatterson
2023-08-09 08:04:19 +10:00
parent 402dd40b2a
commit b88968fada
4 changed files with 105 additions and 46 deletions
@@ -100,6 +100,21 @@ alongside the entity's source code, the value of this annotation can point to an
absolute URL, matching the location reference string format outlined above, for
example: `url:https://github.com/backstage/backstage/tree/master`
### backstage.io/techdocs-ref
```yaml
# Example:
metadata:
annotations:
backstage.io/techdocs-external-ref: component:default/example
```
The value of this annotation informs of an external entity that owns the TechDocs.
This allows you to reference TechDocs from a single source without either duplicating
the TechDocs in the TechDocs page or needing multiple builds of the same docs.
This is for situations where you have complex systems where they share a single repo, and likely a single TechDoc location.
### backstage.io/view-url, backstage.io/edit-url
```yaml
@@ -16,32 +16,10 @@
import {
ANNOTATION_EDIT_URL,
ANNOTATION_LOCATION,
CompoundEntityRef,
DEFAULT_NAMESPACE,
stringifyEntityRef,
} from '@backstage/catalog-model';
import {
HeaderIconLinkRow,
IconLinkVerticalProps,
InfoCardVariants,
Link,
} from '@backstage/core-components';
import {
alertApiRef,
errorApiRef,
useApi,
useApp,
useRouteRef,
} from '@backstage/core-plugin-api';
import {
ScmIntegrationIcon,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
import {
catalogApiRef,
getEntitySourceLocation,
useEntity,
} from '@backstage/plugin-catalog-react';
import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import {
Card,
CardContent,
@@ -50,14 +28,38 @@ import {
IconButton,
makeStyles,
} from '@material-ui/core';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import {
HeaderIconLinkRow,
IconLinkVerticalProps,
InfoCardVariants,
Link,
} from '@backstage/core-components';
import React, { useCallback } from 'react';
import {
ScmIntegrationIcon,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
import {
alertApiRef,
errorApiRef,
useApi,
useApp,
useRouteRef,
} from '@backstage/core-plugin-api';
import {
catalogApiRef,
getEntitySourceLocation,
useEntity,
} from '@backstage/plugin-catalog-react';
import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes';
import { AboutContent } from './AboutContent';
import CachedIcon from '@material-ui/icons/Cached';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
import DocsIcon from '@material-ui/icons/Description';
import EditIcon from '@material-ui/icons/Edit';
import React, { useCallback } from 'react';
import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes';
import { AboutContent } from './AboutContent';
import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import { parseEntityRef } from '@backstage/catalog-model';
const useStyles = makeStyles({
gridItemCard: {
@@ -110,6 +112,19 @@ export function AboutCard(props: AboutCardProps) {
const entityMetadataEditUrl =
entity.metadata.annotations?.[ANNOTATION_EDIT_URL];
let techdocsRef: CompoundEntityRef | undefined;
if (entity.metadata.annotations?.['backstage.io/techdocs-external-ref']) {
try {
techdocsRef = parseEntityRef(
entity.metadata.annotations?.['backstage.io/techdocs-external-ref'],
);
// not a fan of this but we don't care if the parseEntityRef fails
} catch {
techdocsRef = undefined;
}
}
const viewInSource: IconLinkVerticalProps = {
label: 'View Source',
disabled: !entitySourceLocation,
@@ -119,16 +134,24 @@ export function AboutCard(props: AboutCardProps) {
const viewInTechDocs: IconLinkVerticalProps = {
label: 'View TechDocs',
disabled:
!entity.metadata.annotations?.['backstage.io/techdocs-ref'] ||
!viewTechdocLink,
!(
entity.metadata.annotations?.['backstage.io/techdocs-ref'] ||
entity.metadata.annotations?.['backstage.io/techdocs-external-ref']
) || !viewTechdocLink,
icon: <DocsIcon />,
href:
viewTechdocLink &&
viewTechdocLink({
namespace: entity.metadata.namespace || DEFAULT_NAMESPACE,
kind: entity.kind,
name: entity.metadata.name,
}),
(techdocsRef
? viewTechdocLink({
namespace: techdocsRef.namespace || DEFAULT_NAMESPACE,
kind: techdocsRef.kind,
name: techdocsRef.name,
})
: viewTechdocLink({
namespace: entity.metadata.namespace || DEFAULT_NAMESPACE,
kind: entity.kind,
name: entity.metadata.name,
})),
};
const subHeaderLinks = [viewInSource, viewInTechDocs];
+18 -5
View File
@@ -14,18 +14,31 @@
* limitations under the License.
*/
import {
Entity,
getCompoundEntityRef,
parseEntityRef,
} from '@backstage/catalog-model';
import React from 'react';
import { Entity, getCompoundEntityRef } from '@backstage/catalog-model';
import { TechDocsReaderPage } from './plugin';
import { TechDocsReaderPageSubheader } from './reader/components/TechDocsReaderPageSubheader';
import { TechDocsReaderPageContent } from './reader/components/TechDocsReaderPageContent';
import { TechDocsReaderPageSubheader } from './reader/components/TechDocsReaderPageSubheader';
type EntityPageDocsProps = { entity: Entity };
export const EntityPageDocs = ({ entity }: EntityPageDocsProps) => {
const entityRef = getCompoundEntityRef(entity);
let entityRef = getCompoundEntityRef(entity);
if (entity.metadata.annotations?.['backstage.io/techdocs-external-ref']) {
try {
entityRef = parseEntityRef(
entity.metadata.annotations?.['backstage.io/techdocs-external-ref'],
);
} catch {
// not a fan of this but we don't care if the parseEntityRef fails
}
}
return (
<TechDocsReaderPage entityRef={entityRef}>
+14 -6
View File
@@ -18,22 +18,24 @@ import React, { PropsWithChildren } from 'react';
import { Route, Routes, useRoutes } from 'react-router-dom';
import { Entity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
import { EntityPageDocs } from './EntityPageDocs';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
import { TechDocsIndexPage } from './home/components/TechDocsIndexPage';
import { TechDocsReaderPage } from './reader/components/TechDocsReaderPage';
import { useEntity } from '@backstage/plugin-catalog-react';
const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref';
const TECHDOCS_EXTERNAL_ANNOTATION = 'backstage.io/techdocs-external-ref';
/**
* Helper that takes in entity and returns true/false if TechDocs is available for the entity
*
* @public
*/
export const isTechDocsAvailable = (entity: Entity) =>
Boolean(entity?.metadata?.annotations?.[TECHDOCS_ANNOTATION]);
Boolean(entity?.metadata?.annotations?.[TECHDOCS_ANNOTATION]) ||
Boolean(entity?.metadata?.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]);
/**
* Responsible for registering routes for TechDocs, TechDocs Homepage and separate TechDocs page
@@ -75,10 +77,16 @@ export const EmbeddedDocsRouter = (props: PropsWithChildren<{}>) => {
},
]);
const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION];
const projectId =
entity.metadata.annotations?.[TECHDOCS_ANNOTATION] ||
entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION];
if (!projectId) {
return <MissingAnnotationEmptyState annotation={TECHDOCS_ANNOTATION} />;
return (
<MissingAnnotationEmptyState
annotation={[TECHDOCS_ANNOTATION, TECHDOCS_EXTERNAL_ANNOTATION]}
/>
);
}
return element;