Merge pull request #11087 from backstage/camilal/techdocs-addons-app-intergration
[TechDocs] Integrate add-ons with create-app
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-module-addons-contrib': patch
|
||||
---
|
||||
|
||||
Improved inline/type documentation for the <ReportIssue /> addon.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Integrates TechDocs add-ons with the app package so add-ons are configured when creating an app using the Backstage CLI. To apply these changes to an existing application do the following:
|
||||
|
||||
1. Add the `@backstage/plugin-techdocs-react` and `@backstage/plugin-techdocs-module-addons-contrib` packages to your app's dependencies;
|
||||
2. And then register the `<ReportIssue/ >` Addon in your `packages/app/src/App.tsx` file, there where you define a route to `<TechDocsReaderPage />`:
|
||||
|
||||
```diff
|
||||
import {
|
||||
DefaultTechDocsHome,
|
||||
TechDocsIndexPage,
|
||||
TechDocsReaderPage,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
+ import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
+ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
|
||||
// ...
|
||||
|
||||
const AppRoutes = () => {
|
||||
<FlatRoutes>
|
||||
// ... other plugin routes
|
||||
<Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
<DefaultTechDocsHome />
|
||||
</Route>
|
||||
<Route
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
>
|
||||
+ <TechDocsAddons>
|
||||
+ <ReportIssue />
|
||||
+ </TechDocsAddons>
|
||||
</Route>
|
||||
</FlatRoutes>;
|
||||
};
|
||||
```
|
||||
@@ -38,6 +38,7 @@ describe('TechDocs', () => {
|
||||
|
||||
it('should navigate to the TechDocs home page from the URL', () => {
|
||||
cy.visit('/docs');
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
cy.contains('Documentation');
|
||||
@@ -45,15 +46,12 @@ describe('TechDocs', () => {
|
||||
|
||||
it('should navigate to a specific TechDocs entity from the "Overview" tab', () => {
|
||||
cy.visit('/docs');
|
||||
cy.contains('techdocs-e2e-fixture')
|
||||
.parents()
|
||||
.eq(2)
|
||||
.contains('Read Docs')
|
||||
.click();
|
||||
|
||||
cy.contains('techdocs-e2e-fixture').click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
'/docs/default/Component/techdocs-e2e-fixture',
|
||||
'/docs/default/component/techdocs-e2e-fixture',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -84,7 +82,6 @@ describe('TechDocs', () => {
|
||||
cy.visit(
|
||||
'/docs/default/Component/techdocs-e2e-fixture/sub-page-two#section-23',
|
||||
);
|
||||
cy.waitSectionTwoPage();
|
||||
|
||||
// This is used to test the post-render behavior of the techdocs Reader
|
||||
cy.wait(500);
|
||||
@@ -101,10 +98,43 @@ describe('TechDocs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rendering TechDocs Addons', () => {
|
||||
it('should render a content addon', () => {
|
||||
cy.visit('/docs/default/Component/techdocs-e2e-fixture');
|
||||
|
||||
cy.contains('e2e Fixture Documentation');
|
||||
|
||||
// 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%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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigating within TechDocs', () => {
|
||||
it('should navigate to a specific TechDocs page via the navigation bar', () => {
|
||||
cy.visit('/docs/default/Component/techdocs-e2e-fixture');
|
||||
cy.waitHomePage();
|
||||
|
||||
cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.getTechDocsNavigation()
|
||||
@@ -118,14 +148,18 @@ describe('TechDocs', () => {
|
||||
describe('Navigating within a TechDocs page', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/docs/default/Component/techdocs-e2e-fixture/sub-page-two');
|
||||
cy.waitSectionTwoPage();
|
||||
});
|
||||
|
||||
it('should navigate to a specific fragment within the page via the table of contents - Level 1', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
// Section 3
|
||||
cy.getTechDocsTableOfContents().within(() => {
|
||||
cy.get('> div > div > nav > ul > li:nth-child(3) > a').click();
|
||||
});
|
||||
cy.getTechDocsTableOfContents()
|
||||
.find('a')
|
||||
.contains('Section 2.3')
|
||||
.click();
|
||||
|
||||
// wait scroll timeout
|
||||
cy.wait(500);
|
||||
|
||||
cy.isInViewport('#section-23');
|
||||
});
|
||||
@@ -136,20 +170,20 @@ describe('TechDocs', () => {
|
||||
cy.isNotInViewport('#sub-section-222');
|
||||
// Section 2.2
|
||||
cy.getTechDocsTableOfContents()
|
||||
.find(
|
||||
'> div > div > nav > ul > li:nth-child(2) > nav > ul > li:nth-child(2) > a',
|
||||
)
|
||||
.find('a')
|
||||
.contains('Section 2.2.2')
|
||||
.click();
|
||||
|
||||
// wait scroll timeout
|
||||
cy.wait(500);
|
||||
|
||||
cy.isInViewport('#sub-section-222');
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to a specific TechDocs page fragment from a link', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.get('.md-content > article')
|
||||
.contains('Link to Section 1.1')
|
||||
.click();
|
||||
cy.get('article').contains('Link to Section 1.1').click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
@@ -162,7 +196,7 @@ describe('TechDocs', () => {
|
||||
|
||||
it('should navigate to the next page within a TechDocs entity', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.get('.md-footer-nav__link--next').click();
|
||||
cy.get('footer a').contains('Sub-page 3').click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
@@ -174,7 +208,7 @@ describe('TechDocs', () => {
|
||||
|
||||
it('should navigate to the previous page within a TechDocs entity', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.get('.md-footer-nav__link--prev').click();
|
||||
cy.get('footer a').contains('Sub-page 1').click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
|
||||
@@ -25,7 +25,7 @@ Cypress.Commands.add('loginAsGuest', () => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getTechDocsShadowRoot', () => {
|
||||
cy.get('[data-testid="techdocs-content-shadowroot"]').shadow();
|
||||
cy.get('[data-testid="techdocs-native-shadowroot"]').shadow();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('isNotInViewport', element => {
|
||||
@@ -57,11 +57,11 @@ Cypress.Commands.add('isInViewport', element => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getTechDocsTableOfContents', () => {
|
||||
cy.get('[data-md-component="toc"]');
|
||||
cy.get('[data-md-type="toc"]');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getTechDocsNavigation', () => {
|
||||
cy.get('[data-md-component="navigation"]');
|
||||
cy.get('[data-md-type="navigation"]');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('mockSockJSNode', () => {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -54,8 +54,48 @@ const AppRoutes = () => {
|
||||
};
|
||||
```
|
||||
|
||||
That's it! But now, we need the TechDocs Backend plugin for the frontend to
|
||||
work.
|
||||
It would be nice to decorate your pages with something else... Having a link that redirects you to a new issue page when you highlight text in your documentation would be really cool, right? Let's learn how to do this using the TechDocs Addon Framework!
|
||||
|
||||
With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `<ReportIssue />` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed:
|
||||
|
||||
```diff
|
||||
import {
|
||||
DefaultTechDocsHome,
|
||||
TechDocsIndexPage,
|
||||
TechDocsReaderPage,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
+ import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
+ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
|
||||
// ...
|
||||
|
||||
const AppRoutes = () => {
|
||||
<FlatRoutes>
|
||||
// ... other plugin routes
|
||||
<Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
<DefaultTechDocsHome />
|
||||
</Route>
|
||||
<Route
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
>
|
||||
+ <TechDocsAddons>
|
||||
+ <ReportIssue />
|
||||
+ </TechDocsAddons>
|
||||
</Route>
|
||||
</FlatRoutes>;
|
||||
};
|
||||
```
|
||||
|
||||
I know, you're curious to see how it looks, aren't you? See the image below:
|
||||
|
||||
<img data-zoomable src="../../assets/techdocs/report-issue-addon.png" alt="TechDocs Report Issue Add-on" />
|
||||
|
||||
By clicking the open new issue button, you will be redirected to the new issue page according to the source code provider you are using:
|
||||
|
||||
<img data-zoomable src="../../assets/techdocs/report-issue-template.png" alt="TechDocs Report Issue Template" />
|
||||
|
||||
That's it! Now, we need the TechDocs Backend plugin for the frontend to work.
|
||||
|
||||
## Adding TechDocs Backend plugin
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
"@backstage/plugin-search-backend-node": "",
|
||||
"@backstage/plugin-tech-radar": "",
|
||||
"@backstage/plugin-techdocs": "",
|
||||
"@backstage/plugin-techdocs-react": "",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "",
|
||||
"@backstage/plugin-techdocs-backend": "",
|
||||
"@backstage/plugin-user-settings": "",
|
||||
"@backstage/test-utils": "",
|
||||
|
||||
@@ -75,6 +75,8 @@ import { version as pluginSearchBackendModulePg } from '../../../../plugins/sear
|
||||
import { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json';
|
||||
import { version as pluginTechRadar } from '../../../../plugins/tech-radar/package.json';
|
||||
import { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json';
|
||||
import { version as pluginTechdocsReact } from '../../../../plugins/techdocs-react/package.json';
|
||||
import { version as pluginTechdocsModuleAddonsContrib } from '../../../../plugins/techdocs-module-addons-contrib/package.json';
|
||||
import { version as pluginTechdocsBackend } from '../../../../plugins/techdocs-backend/package.json';
|
||||
import { version as pluginUserSettings } from '../../../../plugins/user-settings/package.json';
|
||||
|
||||
@@ -120,6 +122,9 @@ export const packageVersions = {
|
||||
'@backstage/plugin-search-backend-node': pluginSearchBackendNode,
|
||||
'@backstage/plugin-tech-radar': pluginTechRadar,
|
||||
'@backstage/plugin-techdocs': pluginTechdocs,
|
||||
'@backstage/plugin-techdocs-react': pluginTechdocsReact,
|
||||
'@backstage/plugin-techdocs-module-addons-contrib':
|
||||
pluginTechdocsModuleAddonsContrib,
|
||||
'@backstage/plugin-techdocs-backend': pluginTechdocsBackend,
|
||||
'@backstage/plugin-user-settings': pluginUserSettings,
|
||||
'@backstage/test-utils': testUtils,
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
"@backstage/plugin-search-react": "^{{version '@backstage/plugin-search-react'}}",
|
||||
"@backstage/plugin-tech-radar": "^{{version '@backstage/plugin-tech-radar'}}",
|
||||
"@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}",
|
||||
"@backstage/plugin-techdocs-react": "^{{version '@backstage/plugin-techdocs-react'}}",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "^{{version '@backstage/plugin-techdocs-module-addons-contrib'}}",
|
||||
"@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}",
|
||||
"@backstage/theme": "^{{version '@backstage/theme'}}",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
techdocsPlugin,
|
||||
TechDocsReaderPage,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
import { UserSettingsPage } from '@backstage/plugin-user-settings';
|
||||
import { apis } from './apis';
|
||||
import { entityPage } from './components/catalog/EntityPage';
|
||||
@@ -68,7 +70,11 @@ const routes = (
|
||||
<Route
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
/>
|
||||
>
|
||||
<TechDocsAddons>
|
||||
<ReportIssue />
|
||||
</TechDocsAddons>
|
||||
</Route>
|
||||
<Route path="/create" element={<ScaffolderPage />} />
|
||||
<Route path="/api-docs" element={<ApiExplorerPage />} />
|
||||
<Route
|
||||
|
||||
@@ -82,9 +82,74 @@ export const ExpandableNavigation = techdocsModuleAddonsContribPlugin.provide(
|
||||
/**
|
||||
* TechDocs addon that lets you select text and open GitHub/Gitlab issues
|
||||
*
|
||||
* @remarks
|
||||
* Before using it, you should set up an `edit_uri` for your pages as explained {@link https://backstage.io/docs/features/techdocs/faqs#is-it-possible-for-users-to-suggest-changes-or-provide-feedback-on-a-techdocs-page | here} and remember, it only works for Github or Gitlab.
|
||||
*
|
||||
* @example
|
||||
* Here's a simple example:
|
||||
* ```
|
||||
* import {
|
||||
* DefaultTechDocsHome,
|
||||
* TechDocsIndexPage,
|
||||
* TechDocsReaderPage,
|
||||
* } from '@backstage/plugin-techdocs';
|
||||
* import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
* import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
*
|
||||
*
|
||||
* const AppRoutes = () => {
|
||||
* <FlatRoutes>
|
||||
* // other plugin routes
|
||||
* <Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
* <DefaultTechDocsHome />
|
||||
* </Route>
|
||||
* <Route
|
||||
* path="/docs/:namespace/:kind/:name/*"
|
||||
* element={<TechDocsReaderPage />}
|
||||
* >
|
||||
* <TechDocsAddons>
|
||||
* <ReportIssue />
|
||||
* </TechDocsAddons>
|
||||
* </Route>
|
||||
* </FlatRoutes>;
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* Here's an example with `debounceTime` and `templateBuilder` props:
|
||||
* ```
|
||||
* import {
|
||||
* DefaultTechDocsHome,
|
||||
* TechDocsIndexPage,
|
||||
* TechDocsReaderPage,
|
||||
* } from '@backstage/plugin-techdocs';
|
||||
* import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
* import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
*
|
||||
* const templateBuilder = ({ selection }: ReportIssueTemplateBuilder) => (({
|
||||
* title: 'Custom issue title',
|
||||
* body: `Custom issue body: ${selection.toString()}`
|
||||
* }))
|
||||
*
|
||||
* const AppRoutes = () => {
|
||||
* <FlatRoutes>
|
||||
* // other plugin routes
|
||||
* <Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
* <DefaultTechDocsHome />
|
||||
* </Route>
|
||||
* <Route
|
||||
* path="/docs/:namespace/:kind/:name/*"
|
||||
* element={<TechDocsReaderPage />}
|
||||
* >
|
||||
* <TechDocsAddons>
|
||||
* <ReportIssue debounceTime={300} templateBuilder={templateBuilder} />
|
||||
* </TechDocsAddons>
|
||||
* </Route>
|
||||
* </FlatRoutes>;
|
||||
* ```
|
||||
* @param props - Object that can optionally contain `debounceTime` and `templateBuilder` properties.
|
||||
* @public
|
||||
*/
|
||||
|
||||
export const ReportIssue = techdocsModuleAddonsContribPlugin.provide(
|
||||
createTechDocsAddonExtension<ReportIssueProps>({
|
||||
name: 'ReportIssue',
|
||||
@@ -107,7 +172,7 @@ export const ReportIssue = techdocsModuleAddonsContribPlugin.provide(
|
||||
* TechDocsIndexPage,
|
||||
* TechDocsReaderPage,
|
||||
* } from '@backstage/plugin-techdocs';
|
||||
* import { TechDocsAddons } from '@backstage/plugin-techdocs-react/alpha';
|
||||
* import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
* import { TextSize } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
*
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user