From c7cb4c02f1d5c720c983b34dcf9b09970e658947 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Wed, 24 Jul 2024 21:51:54 +0100 Subject: [PATCH] techdocs: add ability to override entity content empty state Signed-off-by: MT Lewis --- .changeset/tall-taxis-flow.md | 5 +++ plugins/techdocs/api-report-alpha.md | 64 ++++++++++++++++++++++++++-- plugins/techdocs/api-report.md | 16 +++++-- plugins/techdocs/src/Router.tsx | 12 ++++-- plugins/techdocs/src/alpha.tsx | 54 +++++++++++++++++++---- 5 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 .changeset/tall-taxis-flow.md diff --git a/.changeset/tall-taxis-flow.md b/.changeset/tall-taxis-flow.md new file mode 100644 index 0000000000..2a785e7720 --- /dev/null +++ b/.changeset/tall-taxis-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Add `element:techdocs/entity-content-empty-state` extension to allow overriding the empty state for the entity page techdocs tab. diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index 6a22a9fce9..307a409b76 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -164,9 +164,6 @@ const _default: FrontendPlugin< inputs: {}; }>; 'entity-content:techdocs': ExtensionDefinition<{ - kind: 'entity-content'; - namespace: undefined; - name: undefined; config: { path: string | undefined; title: string | undefined; @@ -210,12 +207,71 @@ const _default: FrontendPlugin< optional: true; } >; - inputs: {}; + inputs: { + emptyState: ExtensionInput< + ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + >, + { + singleton: true; + optional: true; + } + >; + }; + kind: 'entity-content'; + namespace: undefined; + name: undefined; + }>; + 'element:techdocs/entity-content-empty-state': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'element'; + namespace: undefined; + name: 'entity-content-empty-state'; }>; } >; export default _default; +// @alpha (undocumented) +export const TechDocsEntityContentEmptyState: ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'element'; + namespace: undefined; + name: 'entity-content-empty-state'; +}>; + // @alpha (undocumented) export const techDocsSearchResultListItemExtension: ExtensionDefinition<{ config: { diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 781d8db422..0bde0ebc21 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -16,8 +16,10 @@ import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { JSXElementConstructor } from 'react'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; +import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { ResultHighlight } from '@backstage/plugin-search-common'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -133,7 +135,9 @@ export type DocsTableRow = { // @public export const EmbeddedDocsRouter: ( - props: PropsWithChildren<{}>, + props: PropsWithChildren<{ + emptyState?: React_2.ReactElement; + }>, ) => React_2.JSX.Element | null; // @public @@ -190,9 +194,13 @@ export type EntityListDocsTableProps = { }; // @public -export const EntityTechdocsContent: (props: { - children?: ReactNode; -}) => JSX_2.Element | null; +export const EntityTechdocsContent: ( + props: PropsWithChildren<{ + emptyState?: + | ReactElement> + | undefined; + }>, +) => JSX_2.Element | null; // @public export const isTechDocsAvailable: (entity: Entity) => boolean; diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx index 87a53156d6..ab6267247a 100644 --- a/plugins/techdocs/src/Router.tsx +++ b/plugins/techdocs/src/Router.tsx @@ -61,8 +61,10 @@ export const Router = () => { * * @public */ -export const EmbeddedDocsRouter = (props: PropsWithChildren<{}>) => { - const { children } = props; +export const EmbeddedDocsRouter = ( + props: PropsWithChildren<{ emptyState?: React.ReactElement }>, +) => { + const { children, emptyState } = props; const { entity } = useEntity(); // Using objects instead of elements, otherwise "outlet" will be null on sub-pages and add-ons won't render @@ -84,7 +86,11 @@ export const EmbeddedDocsRouter = (props: PropsWithChildren<{}>) => { entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]; if (!projectId) { - return ; + return ( + emptyState ?? ( + + ) + ); } return element; diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha.tsx index b0d0d3d467..bfd81f88eb 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha.tsx @@ -21,8 +21,10 @@ import { ApiBlueprint, PageBlueprint, NavItemBlueprint, + createExtensionInput, + coreExtensionData, + createExtension, } from '@backstage/frontend-plugin-api'; -import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; import { configApiRef, createApiFactory, @@ -34,6 +36,10 @@ import { convertLegacyRouteRef, convertLegacyRouteRefs, } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react'; +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; +import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; import { techdocsApiRef, techdocsStorageApiRef, @@ -44,7 +50,6 @@ import { rootDocsRouteRef, rootRouteRef, } from './routes'; -import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; /** @alpha */ const techDocsStorageApi = ApiBlueprint.make({ @@ -152,13 +157,45 @@ const techDocsReaderPage = PageBlueprint.make({ * * @alpha */ -const techDocsEntityContent = EntityContentBlueprint.make({ - params: { - defaultPath: 'docs', - defaultTitle: 'TechDocs', - loader: () => - import('./Router').then(m => compatWrapper()), +const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ + inputs: { + emptyState: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + optional: true, + }), }, + factory(originalFactory, context) { + return originalFactory( + { + defaultPath: 'docs', + defaultTitle: 'TechDocs', + loader: () => + import('./Router').then(({ EmbeddedDocsRouter }) => + compatWrapper( + , + ), + ), + }, + context, + ); + }, +}); + +/** @alpha */ +export const TechDocsEntityContentEmptyState = createExtension({ + kind: 'element', + name: 'entity-content-empty-state', + attachTo: { id: 'entity-content:techdocs', input: 'emptyState' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement( + , + ), + ], }); /** @alpha */ @@ -180,6 +217,7 @@ export default createFrontendPlugin({ techDocsPage, techDocsReaderPage, techDocsEntityContent, + TechDocsEntityContentEmptyState, techDocsSearchResultListItemExtension, ], routes: convertLegacyRouteRefs({