diff --git a/.changeset/lucky-masks-talk.md b/.changeset/lucky-masks-talk.md new file mode 100644 index 0000000000..e15bfafcc2 --- /dev/null +++ b/.changeset/lucky-masks-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Improved inline/type documentation for the addon. diff --git a/.changeset/spotty-papayas-shave.md b/.changeset/spotty-papayas-shave.md new file mode 100644 index 0000000000..5a741b6b33 --- /dev/null +++ b/.changeset/spotty-papayas-shave.md @@ -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 `` Addon in your `packages/app/src/App.tsx` file, there where you define a route to ``: + +```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 = () => { + + // ... other plugin routes + }> + + + } + > ++ ++ ++ + + ; +}; +``` diff --git a/cypress/src/integration/plugins/techdocs.spec.ts b/cypress/src/integration/plugins/techdocs.spec.ts index d6049ea53d..1e69ba46ad 100644 --- a/cypress/src/integration/plugins/techdocs.spec.ts +++ b/cypress/src/integration/plugins/techdocs.spec.ts @@ -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( diff --git a/cypress/src/support/commands.ts b/cypress/src/support/commands.ts index 666dc157d7..9973afb6a7 100644 --- a/cypress/src/support/commands.ts +++ b/cypress/src/support/commands.ts @@ -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', () => { diff --git a/docs/assets/techdocs/report-issue-addon.png b/docs/assets/techdocs/report-issue-addon.png new file mode 100644 index 0000000000..4c61935f2f Binary files /dev/null and b/docs/assets/techdocs/report-issue-addon.png differ diff --git a/docs/assets/techdocs/report-issue-template.png b/docs/assets/techdocs/report-issue-template.png new file mode 100644 index 0000000000..35dca05e1f Binary files /dev/null and b/docs/assets/techdocs/report-issue-template.png differ diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 1eeebae903..f8bf98c0e0 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -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 `` 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 = () => { + + // ... other plugin routes + }> + + + } + > ++ ++ ++ + + ; +}; +``` + +I know, you're curious to see how it looks, aren't you? See the image below: + +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: + +TechDocs Report Issue Template + +That's it! Now, we need the TechDocs Backend plugin for the frontend to work. ## Adding TechDocs Backend plugin diff --git a/packages/create-app/package.json b/packages/create-app/package.json index cf3bafecd8..6d1d2ba402 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -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": "", diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index b6c93a38b3..25007d7535 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -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, diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index 87a8060594..9bf67f6e52 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -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", diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index f4ff424926..c4877263f0 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -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 = ( } - /> + > + + + + } /> } /> { + * + * // other plugin routes + * }> + * + * + * } + * > + * + * + * + * + * ; + * }; + * ``` + * + * @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 = () => { + * + * // other plugin routes + * }> + * + * + * } + * > + * + * + * + * + * ; + * ``` + * @param props - Object that can optionally contain `debounceTime` and `templateBuilder` properties. * @public */ - export const ReportIssue = techdocsModuleAddonsContribPlugin.provide( createTechDocsAddonExtension({ 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'; * *