Merge pull request #14323 from agentbellnorm/fix-techdocs-cli-serve

[TechDocs] Fix techdocs cli serve
This commit is contained in:
Otto Sichert
2022-10-26 10:53:32 +02:00
committed by GitHub
2 changed files with 90 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Fix logic bug that broke techdocs-cli-embedded-app
@@ -35,6 +35,82 @@ import {
useRouteRefParams,
} from '@backstage/core-plugin-api';
/* An explanation for the multiple ways of customizing the TechDocs reader page
Please refer to this page on the microsite for the latest recommended approach:
https://backstage.io/docs/features/techdocs/how-to-guides#how-to-customize-the-techdocs-reader-page
The <TechDocsReaderPage> component is responsible for rendering the <TechDocsReaderPageProvider> and
its contained version of a <Page>, which in turn renders the <TechDocsReaderPageContent>.
Historically, there have been different approaches on how this <Page> can be customized, and how the
<TechDocsReaderPageContent> inside could be exchanged for a custom implementation (which was not
possible before). Also, the current implementation supports every scenario to avoid breaking default
configurations of TechDocs.
In particular, there are 4 different TechDocs page configurations:
CONFIGURATION 1: <TechDocsReaderPage> only, no children
<Route path="/docs/:namespace/:kind/:name/*" element={<TechDocsReaderPage />} >
This is the simplest way to use TechDocs. Only a full page is passed, assuming that it comes with
its content inside. Since we allowed customizing it, we started providing <TechDocsReaderLayout> as
a default implementation (which contains <TechDocsReaderPageContent>).
CONFIGURATION 2 (not advised): <TechDocsReaderPage> with element children
<Route
path="/docs/:namespace/:kind/:name/*"
element={
<TechDocsReaderPage>
{techdocsPage}
</TechDocsReaderPage>
}
/>
Previously, there were two ways of passing children to <TechDocsReaderPage>: either as elements (as
shown above), or as a render function (described below in CONFIGURATION 3). The "techdocsPage" is
located in packages/app/src/components/techdocs and is the default implementation of the content
inside.
CONFIGURATION 3 (not advised): <TechDocsReaderPage> with render function as child
<Route
path="/docs/:namespace/:kind/:name/*"
element={
<TechDocsReaderPage>
{({ metadata, entityMetadata, onReady }) => (
techdocsPage
)}
</TechDocsReaderPage>
}
/>
Similar to CONFIGURATION 2, the direct children will be passed to the <TechDocsReaderPage> but in
this case interpreted as render prop.
CONFIGURATION 4: <TechDocsReaderPage> and provided content in <Route>
<Route
path="/docs/:namespace/:kind/:name/*"
element={<TechDocsReaderPage />}
>
{techDocsPage}
<TechDocsAddons>
<ExpandableNavigation />
<ReportIssue />
<TextSize />
</TechDocsAddons>
</Route>
This is the current state in packages/app/src/App.tsx and moved the location of children from inside
the element prop in the <Route> to the children of the <Route>. Then, in <TechDocsReaderPage> they
are retrieved using the useOutlet hook from React Router.
NOTE: Render functions are no longer supported in this approach.
*/
/**
* Props for {@link TechDocsReaderLayout}
* @public
@@ -77,6 +153,7 @@ export type TechDocsReaderPageProps = {
/**
* An addon-aware implementation of the TechDocsReaderPage.
*
* @public
*/
export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {
@@ -88,21 +165,14 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {
if (!children) {
const childrenList = outlet ? Children.toArray(outlet.props.children) : [];
const page = childrenList.find(child => {
if (getComponentData(child, TECHDOCS_ADDONS_WRAPPER_KEY)) {
return false;
}
// react-router 6 stable wraps children in a routing context provider, so check one level deeper
const nestedChildren = (child as ReactElement)?.props?.children;
if (nestedChildren) {
return !Children.toArray(nestedChildren).some(nested =>
getComponentData(nested, TECHDOCS_ADDONS_WRAPPER_KEY),
);
}
return true;
});
const grandChildren = childrenList.flatMap(
child => (child as ReactElement)?.props?.children ?? [],
);
const page: React.ReactNode = grandChildren.find(
grandChild => !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY),
);
// As explained above, "page" is configuration 4 and <TechDocsReaderLayout> is 1
return (
<TechDocsReaderPageProvider entityRef={entityRef}>
{(page as JSX.Element) || <TechDocsReaderLayout />}
@@ -110,6 +180,7 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => {
);
}
// As explained above, a render function is configuration 3 and React element is 2
return (
<TechDocsReaderPageProvider entityRef={entityRef}>
{({ metadata, entityMetadata, onReady }) => (