diff --git a/.changeset/tall-taxis-flow.md b/.changeset/tall-taxis-flow.md new file mode 100644 index 0000000000..f5596c8c55 --- /dev/null +++ b/.changeset/tall-taxis-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +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 6a22a9fce9..dfe5dc2cc2 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,7 +207,45 @@ 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; + }>; + 'empty-state:techdocs/entity-content': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + { + optional: true; + } + >; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'empty-state'; + namespace: undefined; + name: 'entity-content'; }>; } >; diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 781d8db422..c58a946287 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -134,7 +134,7 @@ export type DocsTableRow = { // @public export const EmbeddedDocsRouter: ( props: PropsWithChildren<{}>, -) => React_2.JSX.Element | null; +) => React_2.JSX.Element; // @public export const EntityListDocsGrid: ( @@ -192,7 +192,7 @@ export type EntityListDocsTableProps = { // @public export const EntityTechdocsContent: (props: { children?: ReactNode; -}) => JSX_2.Element | null; +}) => 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 87a53156d6..352c9f5b73 100644 --- a/plugins/techdocs/src/Router.tsx +++ b/plugins/techdocs/src/Router.tsx @@ -56,13 +56,10 @@ export const Router = () => { ); }; -/** - * Responsible for registering route to view docs on Entity page - * - * @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,8 +81,23 @@ export const EmbeddedDocsRouter = (props: PropsWithChildren<{}>) => { entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]; if (!projectId) { - return ; + return ( + emptyState ?? ( + + ) + ); } 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/alpha.tsx b/plugins/techdocs/src/alpha.tsx index b0d0d3d467..2299b56be9 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,8 @@ import { convertLegacyRouteRef, convertLegacyRouteRefs, } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; import { techdocsApiRef, techdocsStorageApiRef, @@ -44,7 +48,6 @@ import { rootDocsRouteRef, rootRouteRef, } from './routes'; -import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; /** @alpha */ const techDocsStorageApi = ApiBlueprint.make({ @@ -152,13 +155,40 @@ 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, + ); + }, +}); + +const techDocsEntityContentEmptyState = createExtension({ + kind: 'empty-state', + name: 'entity-content', + attachTo: { id: 'entity-content:techdocs', input: 'emptyState' }, + output: [coreExtensionData.reactElement.optional()], + factory: () => [], }); /** @alpha */ @@ -180,6 +210,7 @@ export default createFrontendPlugin({ techDocsPage, techDocsReaderPage, techDocsEntityContent, + techDocsEntityContentEmptyState, techDocsSearchResultListItemExtension, ], routes: convertLegacyRouteRefs({ 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, }), );