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:
+
+
+
+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:
+
+
+
+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';
*
*