diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md
index 0a4f5ee400..651520cd28 100644
--- a/docs/features/software-catalog/well-known-annotations.md
+++ b/docs/features/software-catalog/well-known-annotations.md
@@ -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
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
index 06cc020e51..6c28660144 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx
@@ -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: ,
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];
diff --git a/plugins/techdocs/src/EntityPageDocs.tsx b/plugins/techdocs/src/EntityPageDocs.tsx
index b0e529f9a5..56c1341aa2 100644
--- a/plugins/techdocs/src/EntityPageDocs.tsx
+++ b/plugins/techdocs/src/EntityPageDocs.tsx
@@ -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 (
diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx
index f821da2a9f..5c967a54e8 100644
--- a/plugins/techdocs/src/Router.tsx
+++ b/plugins/techdocs/src/Router.tsx
@@ -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 ;
+ return (
+
+ );
}
return element;