Merge pull request #11609 from backstage/camilal/fix-entity-docs-outlet

[TechDocs]: Use object routes on entity docs pages
This commit is contained in:
Camila Belo
2022-05-20 11:42:20 +02:00
committed by GitHub
8 changed files with 255 additions and 17 deletions
+46
View File
@@ -0,0 +1,46 @@
---
'@backstage/create-app': patch
---
Register `TechDocs` addons on catalog entity pages, follow the steps below to add them manually:
```diff
// packages/app/src/components/catalog/EntityPage.tsx
+ import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
+ import {
+ ReportIssue,
+ } from '@backstage/plugin-techdocs-module-addons-contrib';
+ const techdocsContent = (
+ <EntityTechdocsContent>
+ <TechDocsAddons>
+ <ReportIssue />
+ </TechDocsAddons>
+ </EntityTechdocsContent>
+ );
const defaultEntityPage = (
...
<EntityLayout.Route path="/docs" title="Docs">
+ {techdocsContent}
</EntityLayout.Route>
...
);
const serviceEntityPage = (
...
<EntityLayout.Route path="/docs" title="Docs">
+ {techdocsContent}
</EntityLayout.Route>
...
);
const websiteEntityPage = (
...
<EntityLayout.Route path="/docs" title="Docs">
+ {techdocsContent}
</EntityLayout.Route>
...
);
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Fix `EntityTechdocsContent` component to use objects instead of `<Route>` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render.
@@ -82,5 +82,118 @@ describe('Catalog', () => {
.contains('Sub-page 1')
.should('be.visible');
});
it('Should render addons on docs tab homepage', () => {
cy.loginAsGuest();
cy.visit('/catalog');
cy.contains('techdocs-e2e-fixture').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq(
'/catalog/default/component/techdocs-e2e-fixture',
);
});
cy.getCatalogDocsTab().click();
cy.wait(300);
cy.getTechDocsShadowRoot()
.find('h1')
.contains('Home page')
.should('be.visible');
// highlight a snippet of text
cy.getTechDocsShadowRoot()
.find('article > p')
.then($el => {
const el = $el[0];
const document = el.ownerDocument;
const range = document.createRange();
range.selectNodeContents(el);
document?.getSelection()?.removeAllRanges();
document?.getSelection()?.addRange(range);
});
cy.document().trigger('selectionchange');
// wait for new issue default debounce time
cy.wait(600);
// assert that the new issue button has a right url
cy.getTechDocsShadowRoot()
.contains('Open new Github issue')
.should(
'have.attr',
'href',
'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fcatalog%2Fdefault%2Fcomponent%2Ftechdocs-e2e-fixture%2Fdocs%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Findex.md%3E',
);
});
it('Should render addons on docs tab sup-page', () => {
cy.loginAsGuest();
cy.visit('/catalog');
cy.contains('techdocs-e2e-fixture').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq(
'/catalog/default/component/techdocs-e2e-fixture',
);
});
cy.getCatalogDocsTab().click();
cy.wait(300);
cy.getTechDocsShadowRoot()
.find('h1')
.contains('Home page')
.should('be.visible');
cy.getTechDocsShadowRoot().within(() => {
cy.getTechDocsNavigation().find('a').contains('Sub-page 1').click();
});
cy.location().should(loc => {
expect(loc.pathname).to.eq(
'/catalog/default/component/techdocs-e2e-fixture/docs/sub-page-one/',
);
});
cy.getTechDocsShadowRoot()
.find('h1')
.contains('Sub-page 1')
.should('be.visible');
// highlight a snippet of text
cy.getTechDocsShadowRoot()
.find('#section-11')
.then($el => {
const el = $el[0];
const document = el.ownerDocument;
const range = document.createRange();
range.selectNodeContents(el);
document?.getSelection()?.removeAllRanges();
document?.getSelection()?.addRange(range);
});
cy.document().trigger('selectionchange');
// wait for new issue default debounce time
cy.wait(600);
// assert that the new issue button has a right url
cy.getTechDocsShadowRoot()
.contains('Open new Github issue')
.should(
'have.attr',
'href',
'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20Section%201.1%C2%B6&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20Section%201.1%C2%B6%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fcatalog%2Fdefault%2Fcomponent%2Ftechdocs-e2e-fixture%2Fdocs%2Fsub-page-one%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Fsub-page-one.md%3E',
);
});
});
});
@@ -99,7 +99,7 @@ describe('TechDocs', () => {
});
describe('Rendering TechDocs Addons', () => {
it('should render a content addon', () => {
it('should render a content addon in homepage', () => {
cy.visit('/docs/default/Component/techdocs-e2e-fixture');
cy.contains('e2e Fixture Documentation');
@@ -130,6 +130,43 @@ describe('TechDocs', () => {
'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fdocs%2Fdefault%2FComponent%2Ftechdocs-e2e-fixture%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Findex.md%3E',
);
});
it('should render a content addon in sub-pages', () => {
cy.visit('/docs/default/Component/techdocs-e2e-fixture');
cy.contains('e2e Fixture Documentation');
// open sub-page
cy.getTechDocsShadowRoot().within(() => {
cy.getTechDocsNavigation().find('a').contains('Sub-page 1').click();
});
// highlight a snippet of text
cy.getTechDocsShadowRoot()
.find('#section-11')
.then($el => {
const el = $el[0];
const document = el.ownerDocument;
const range = document.createRange();
range.selectNodeContents(el);
document?.getSelection()?.removeAllRanges();
document?.getSelection()?.addRange(range);
});
cy.document().trigger('selectionchange');
// wait for new issue default debounce time
cy.wait(600);
// assert that the new issue button has a right url
cy.getTechDocsShadowRoot()
.contains('Open new Github issue')
.should(
'have.attr',
'href',
'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20Section%201.1%C2%B6&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20Section%201.1%C2%B6%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fdocs%2Fdefault%2FComponent%2Ftechdocs-e2e-fixture%2Fsub-page-one%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Fsub-page-one.md%3E',
);
});
});
describe('Navigating within TechDocs', () => {
@@ -140,6 +140,12 @@ import { EntityGoCdContent, isGoCdAvailable } from '@backstage/plugin-gocd';
import React, { ReactNode, useMemo, useState } from 'react';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import {
TextSize,
ReportIssue,
} from '@backstage/plugin-techdocs-module-addons-contrib';
const customEntityFilterKind = ['Component', 'API', 'System'];
const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
@@ -168,6 +174,15 @@ const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
);
};
const techdocsContent = (
<EntityTechdocsContent>
<TechDocsAddons>
<TextSize />
<ReportIssue />
</TechDocsAddons>
</EntityTechdocsContent>
);
/**
* NOTE: This page is designed to work on small screens such as mobile devices.
* This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`,
@@ -398,7 +413,7 @@ const serviceEntityPage = (
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
{techdocsContent}
</EntityLayout.Route>
<EntityLayout.Route
@@ -465,8 +480,9 @@ const websiteEntityPage = (
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
{techdocsContent}
</EntityLayout.Route>
<EntityLayout.Route
if={isNewRelicDashboardAvailable}
path="/newrelic-dashboard"
@@ -512,7 +528,7 @@ const defaultEntityPage = (
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
{techdocsContent}
</EntityLayout.Route>
<EntityLayout.Route path="/todos" title="TODOs">
@@ -68,6 +68,17 @@ import {
RELATION_PROVIDES_API,
} from '@backstage/catalog-model';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
const techdocsContent = (
<EntityTechdocsContent>
<TechDocsAddons>
<ReportIssue />
</TechDocsAddons>
</EntityTechdocsContent>
);
const cicdContent = (
// This is an example of how you can implement your company's logic in entity page.
// You can for example enforce that all components of type 'service' should use GitHubActions
@@ -167,7 +178,7 @@ const serviceEntityPage = (
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
{techdocsContent}
</EntityLayout.Route>
</EntityLayout>
);
@@ -194,7 +205,7 @@ const websiteEntityPage = (
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
{techdocsContent}
</EntityLayout.Route>
</EntityLayout>
);
@@ -213,7 +224,7 @@ const defaultEntityPage = (
</EntityLayout.Route>
<EntityLayout.Route path="/docs" title="Docs">
<EntityTechdocsContent />
{techdocsContent}
</EntityLayout.Route>
</EntityLayout>
);
+4 -2
View File
@@ -111,7 +111,9 @@ export type DocsTableRow = {
};
// @public
export const EmbeddedDocsRouter: (props: PropsWithChildren<{}>) => JSX.Element;
export const EmbeddedDocsRouter: (
props: PropsWithChildren<{}>,
) => JSX.Element | null;
// @public
export const EntityListDocsGrid: () => JSX.Element;
@@ -153,7 +155,7 @@ export type EntityListDocsTableProps = {
// @public
export const EntityTechdocsContent: (props: {
children?: ReactNode;
}) => JSX.Element;
}) => JSX.Element | null;
// @public
export const isTechDocsAvailable: (entity: Entity) => boolean;
+16 -8
View File
@@ -15,7 +15,7 @@
*/
import React, { PropsWithChildren } from 'react';
import { Route, Routes } from 'react-router-dom';
import { Route, Routes, useRoutes } from 'react-router-dom';
import { Entity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
@@ -61,17 +61,25 @@ export const EmbeddedDocsRouter = (props: PropsWithChildren<{}>) => {
const { children } = props;
const { entity } = useEntity();
// Using objects instead of <Route> elements, otherwise "outlet" will be null on sub-pages and add-ons won't render
const element = useRoutes([
{
path: '/*',
element: <EntityPageDocs entity={entity} />,
children: [
{
path: '/*',
element: children,
},
],
},
]);
const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION];
if (!projectId) {
return <MissingAnnotationEmptyState annotation={TECHDOCS_ANNOTATION} />;
}
return (
<Routes>
<Route path="/*" element={<EntityPageDocs entity={entity} />}>
{children}
</Route>
</Routes>
);
return element;
};