From c7cb4c02f1d5c720c983b34dcf9b09970e658947 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Wed, 24 Jul 2024 21:51:54 +0100 Subject: [PATCH 1/6] 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({ From dc6526f020ac4b240c178e12e2c92b4eebdad3d7 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Mon, 16 Sep 2024 11:53:19 +0100 Subject: [PATCH 2/6] techdocs: stop exporting entity content empty state Signed-off-by: MT Lewis --- plugins/techdocs/api-report-alpha.md | 23 ----------------------- plugins/techdocs/src/alpha.tsx | 3 +-- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index 307a409b76..c1659d76f6 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -249,29 +249,6 @@ const _default: FrontendPlugin< >; 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/src/alpha.tsx b/plugins/techdocs/src/alpha.tsx index bfd81f88eb..a9b2f355f2 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha.tsx @@ -185,8 +185,7 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ }, }); -/** @alpha */ -export const TechDocsEntityContentEmptyState = createExtension({ +const TechDocsEntityContentEmptyState = createExtension({ kind: 'element', name: 'entity-content-empty-state', attachTo: { id: 'entity-content:techdocs', input: 'emptyState' }, From f66ee9c89fb6128d374b42136569a9d9da7ad827 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Mon, 16 Sep 2024 11:55:37 +0100 Subject: [PATCH 3/6] techdocs: rename entity-content empty state extension Signed-off-by: MT Lewis --- .changeset/tall-taxis-flow.md | 2 +- plugins/techdocs/api-report-alpha.md | 6 +++--- plugins/techdocs/src/alpha.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.changeset/tall-taxis-flow.md b/.changeset/tall-taxis-flow.md index 2a785e7720..f5596c8c55 100644 --- a/.changeset/tall-taxis-flow.md +++ b/.changeset/tall-taxis-flow.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs': patch --- -Add `element:techdocs/entity-content-empty-state` extension to allow overriding the empty state for the entity page techdocs tab. +Add `empty-state:techdocs/entity-content` 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 c1659d76f6..ce48a15e3d 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -224,7 +224,7 @@ const _default: FrontendPlugin< namespace: undefined; name: undefined; }>; - 'element:techdocs/entity-content-empty-state': ExtensionDefinition<{ + 'empty-state:techdocs/entity-content': ExtensionDefinition<{ config: {}; configInput: {}; output: ConfigurableExtensionDataRef< @@ -241,9 +241,9 @@ const _default: FrontendPlugin< } >; }; - kind: 'element'; + kind: 'empty-state'; namespace: undefined; - name: 'entity-content-empty-state'; + name: 'entity-content'; }>; } >; diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha.tsx index a9b2f355f2..f443ebea02 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha.tsx @@ -186,8 +186,8 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ }); const TechDocsEntityContentEmptyState = createExtension({ - kind: 'element', - name: 'entity-content-empty-state', + kind: 'empty-state', + name: 'entity-content', attachTo: { id: 'entity-content:techdocs', input: 'emptyState' }, output: [coreExtensionData.reactElement], factory: () => [ From 0e9dbf8d644ac7b7d123c222639f92c3b576d963 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Mon, 16 Sep 2024 12:01:58 +0100 Subject: [PATCH 4/6] techdocs: make empty state output optional Signed-off-by: MT Lewis --- plugins/techdocs/api-report-alpha.md | 4 +++- plugins/techdocs/src/alpha.tsx | 10 ++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index ce48a15e3d..dfe5dc2cc2 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -230,7 +230,9 @@ const _default: FrontendPlugin< output: ConfigurableExtensionDataRef< React_2.JSX.Element, 'core.reactElement', - {} + { + optional: true; + } >; inputs: { [x: string]: ExtensionInput< diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha.tsx index f443ebea02..260909e007 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha.tsx @@ -37,9 +37,7 @@ import { 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, @@ -189,12 +187,8 @@ const TechDocsEntityContentEmptyState = createExtension({ kind: 'empty-state', name: 'entity-content', attachTo: { id: 'entity-content:techdocs', input: 'emptyState' }, - output: [coreExtensionData.reactElement], - factory: () => [ - coreExtensionData.reactElement( - , - ), - ], + output: [coreExtensionData.reactElement.optional()], + factory: () => [], }); /** @alpha */ From 7120d77a33cdd9dd9eec6616e93068b97e330550 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Mon, 16 Sep 2024 12:08:06 +0100 Subject: [PATCH 5/6] techdocs: hide Router emptyState prop in non-alpha public api Signed-off-by: MT Lewis --- plugins/techdocs/api-report.md | 18 +++++------------- plugins/techdocs/src/Router.tsx | 16 +++++++++++----- plugins/techdocs/src/index.ts | 6 +++++- plugins/techdocs/src/plugin.ts | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 0bde0ebc21..c58a946287 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -16,10 +16,8 @@ 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'; @@ -135,10 +133,8 @@ export type DocsTableRow = { // @public export const EmbeddedDocsRouter: ( - props: PropsWithChildren<{ - emptyState?: React_2.ReactElement; - }>, -) => React_2.JSX.Element | null; + props: PropsWithChildren<{}>, +) => React_2.JSX.Element; // @public export const EntityListDocsGrid: ( @@ -194,13 +190,9 @@ export type EntityListDocsTableProps = { }; // @public -export const EntityTechdocsContent: ( - props: PropsWithChildren<{ - emptyState?: - | ReactElement> - | undefined; - }>, -) => JSX_2.Element | null; +export const EntityTechdocsContent: (props: { + children?: ReactNode; +}) => JSX_2.Element; // @public export const isTechDocsAvailable: (entity: Entity) => boolean; diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx index ab6267247a..352c9f5b73 100644 --- a/plugins/techdocs/src/Router.tsx +++ b/plugins/techdocs/src/Router.tsx @@ -56,11 +56,6 @@ export const Router = () => { ); }; -/** - * Responsible for registering route to view docs on Entity page - * - * @public - */ export const EmbeddedDocsRouter = ( props: PropsWithChildren<{ emptyState?: React.ReactElement }>, ) => { @@ -95,3 +90,14 @@ export const EmbeddedDocsRouter = ( return element; }; + +/** + * Responsible for registering route to view docs on Entity page + * + * @public + */ +export const LegacyEmbeddedDocsRouter = (props: PropsWithChildren<{}>) => { + // Wrap the Router to avoid exposing the emptyState prop in the non-alpha + // public API and make it easier for us to change later. + return ; +}; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index c759d43fe9..522bcd0c99 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -41,7 +41,11 @@ export { techdocsPlugin as plugin, techdocsPlugin, } from './plugin'; -export * from './Router'; +export { + isTechDocsAvailable, + LegacyEmbeddedDocsRouter as EmbeddedDocsRouter, + Router, +} from './Router'; export type { TechDocsSearchResultListItemProps } from './search/components/TechDocsSearchResultListItem'; diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index efa6bdfc1f..91b4214336 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -103,7 +103,7 @@ export const TechdocsPage = techdocsPlugin.provide( export const EntityTechdocsContent = techdocsPlugin.provide( createRoutableExtension({ name: 'EntityTechdocsContent', - component: () => import('./Router').then(m => m.EmbeddedDocsRouter), + component: () => import('./Router').then(m => m.LegacyEmbeddedDocsRouter), mountPoint: rootCatalogDocsRouteRef, }), ); From e6d7a26168f6109425ad25176e5df598ca2c5ccc Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Mon, 16 Sep 2024 12:15:34 +0100 Subject: [PATCH 6/6] techdocs: consistent casing for extension variable Signed-off-by: MT Lewis --- plugins/techdocs/src/alpha.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha.tsx index 260909e007..2299b56be9 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha.tsx @@ -183,7 +183,7 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ }, }); -const TechDocsEntityContentEmptyState = createExtension({ +const techDocsEntityContentEmptyState = createExtension({ kind: 'empty-state', name: 'entity-content', attachTo: { id: 'entity-content:techdocs', input: 'emptyState' }, @@ -210,7 +210,7 @@ export default createFrontendPlugin({ techDocsPage, techDocsReaderPage, techDocsEntityContent, - TechDocsEntityContentEmptyState, + techDocsEntityContentEmptyState, techDocsSearchResultListItemExtension, ], routes: convertLegacyRouteRefs({