diff --git a/.changeset/techdocs-eight-camels-hear.md b/.changeset/techdocs-eight-camels-hear.md new file mode 100644 index 0000000000..8f2479f6bd --- /dev/null +++ b/.changeset/techdocs-eight-camels-hear.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Add feedback link icon in Techdocs Reader that directs to GitLab or GitHub repo issue page with pre-filled title and source link. +For link to appear, requires `repo_url` and `edit_uri` to be filled in mkdocs.yml, as per https://www.mkdocs.org/user-guide/configuration. An `edit_uri` will need to be specified for self-hosted GitLab/GitHub instances with a different host name. +To identify issue URL format as GitHub or GitLab, the host name of source in `repo_url` is checked if it contains `gitlab` or `github`. Alternately this is determined by matching to `host` values from `integrations` in app-config.yaml. diff --git a/.changeset/tiny-ears-love.md b/.changeset/tiny-ears-love.md new file mode 100644 index 0000000000..9c3d1613a5 --- /dev/null +++ b/.changeset/tiny-ears-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added support for Datadog rum events diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 226af9bf0a..0472a199ba 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -13,9 +13,11 @@ Cloudformation Codecov Codehilite Config +Datadog Debounce Discoverability Dockerfile +dockerfiles Dockerize Docusaurus Env diff --git a/app-config.yaml b/app-config.yaml index dd1a3f17eb..2af191c56a 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -2,6 +2,12 @@ app: title: Backstage Example App baseUrl: http://localhost:3000 googleAnalyticsTrackingId: # UA-000000-0 + datadogRum: + clientToken: '123456789' + applicationId: qwerty + # site: # datadoghq.eu default = datadoghq.com + # env: # optional + support: url: https://github.com/backstage/backstage/issues # Used by common ErrorPage items: # Used by common SupportButton component diff --git a/contrib/docker/frontend-with-nginx/Dockerfile b/contrib/docker/frontend-with-nginx/Dockerfile deleted file mode 100644 index a444c9de83..0000000000 --- a/contrib/docker/frontend-with-nginx/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM nginx:mainline - -# The purpose of this image is to serve the frontend app content separately. -# By default the Backstage backend uses the app-backend plugin to serve the -# app from the backend itself, but it may be desirable to move the frontend -# content serving to a separate deployment, in which case this image can be used. - -# This dockerfile requires the app to be built on the host first, as it -# simply copies in the build output into the image. - -RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/* - -COPY packages/app/dist /usr/share/nginx/html -COPY docker/default.conf.template /etc/nginx/conf.d/default.conf.template -COPY docker/run.sh /usr/local/bin/run.sh -CMD run.sh - -ENV PORT 80 diff --git a/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild b/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild new file mode 100644 index 0000000000..dda9779eb0 --- /dev/null +++ b/contrib/docker/frontend-with-nginx/Dockerfile.dockerbuild @@ -0,0 +1,59 @@ +# The purpose of this image is to serve the frontend app content separately. +# By default the Backstage backend uses the app-backend plugin to serve the +# app from the backend itself, but it may be desirable to move the frontend +# content serving to a separate deployment, in which case this image can be +# used. + +# This dockerfile also performs the build first inside docker. This may come +# with a build time impact, but is sometimes desirable. If you want to run the +# build on the host instead, use the file simply named Dockerfile in this folder +# instead. + +# USAGE: +# +# - Copy this file and the "docker" folder from this directory to your project +# root +# +# - Update your .dockerignore, make sure that the source folders are not +# excluded, but do exclude node_modules and build artifacts: +# +# .git +# node_modules +# packages/*/dist +# packages/*/node_modules +# plugins/*/dist +# plugins/*/node_modules +# +# - Update the copy of this file to add configuration arguments to the "build" +# command, for example: +# +# RUN yarn workspace app build --config --config ... +# +# - In your project root, run: +# +# docker build -t backstage-frontend -f Dockerfile.dockerbuild . + + + +FROM node:14-buster AS build + +RUN mkdir /app +COPY . /app +WORKDIR /app + +RUN yarn install +RUN yarn workspace app build + + + +FROM nginx:mainline + +RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/* + +COPY --from=build /app/packages/app/dist /usr/share/nginx/html +COPY docker/default.conf.template /etc/nginx/conf.d/default.conf.template +COPY docker/run.sh /usr/local/bin/run.sh + +CMD run.sh + +ENV PORT 80 diff --git a/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild b/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild new file mode 100644 index 0000000000..6e5912dd67 --- /dev/null +++ b/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild @@ -0,0 +1,41 @@ +# The purpose of this image is to serve the frontend app content separately. +# By default the Backstage backend uses the app-backend plugin to serve the +# app from the backend itself, but it may be desirable to move the frontend +# content serving to a separate deployment, in which case this image can be +# used. + +# This dockerfile requires the app to be built on the host first, as it +# simply copies in the build output into the image. If you want to also perform +# the build itself inside docker, use Dockerfile.build in this folder instead. + + +# USAGE: +# +# - Copy this file and the "docker" folder from this directory to your project +# root +# +# - Add the following line to your .dockerignore to make sure that the built +# frontend actually can be transferred into the docker image: +# +# !packages/app/dist +# +# - In your project root, run: +# +# yarn install +# yarn tsc +# yarn build --config --config ... +# docker build -t backstage-frontend -f Dockerfile.hostbuild . + + + +FROM nginx:mainline + +RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/* + +COPY packages/app/dist /usr/share/nginx/html +COPY docker/default.conf.template /etc/nginx/conf.d/default.conf.template +COPY docker/run.sh /usr/local/bin/run.sh + +CMD run.sh + +ENV PORT 80 diff --git a/contrib/docker/frontend-with-nginx/README.md b/contrib/docker/frontend-with-nginx/README.md new file mode 100644 index 0000000000..eae70b8bba --- /dev/null +++ b/contrib/docker/frontend-with-nginx/README.md @@ -0,0 +1,16 @@ +# Frontend with NGINX + +This folder contains Docker images that let you run the Backstage frontend as +a separate image, rather than having it served through the `app-backend` plugin +from the backend. + +Note that when running the frontend like this, the app configuration becomes +embedded into the actual static JavaScript files at build time. This means that +you will have to supply the list of configuration files as part of the command +line at build. + +## Usage + +There are two variants: one that builds inside Docker, and one that builds on +the host. See the comments at the top of the individual dockerfiles for usage +instructions. diff --git a/contrib/docker/multi-stage-frontend/Dockerfile b/contrib/docker/multi-stage-frontend/Dockerfile deleted file mode 100644 index 3190687c52..0000000000 --- a/contrib/docker/multi-stage-frontend/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM node:12-buster AS build - -RUN mkdir /app -COPY . /app -WORKDIR /app - -RUN yarn install -RUN yarn workspace example-app build - -# Contruct backstage-frontend image -FROM nginx:mainline - -RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/* - -# Copy from build stage -COPY --from=build /app/packages/app/dist /usr/share/nginx/html - -COPY docker/default.conf.template /etc/nginx/conf.d/default.conf.template -COPY docker/run.sh /usr/local/bin/run.sh - -CMD run.sh - -ENV PORT 80 diff --git a/contrib/docker/multi-stage-frontend/README.md b/contrib/docker/multi-stage-frontend/README.md deleted file mode 100644 index b6ae4fdc6c..0000000000 --- a/contrib/docker/multi-stage-frontend/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Standalone Dockerfile for frontend - -This directory contains the resources which will help you build backstage without any requirements -other than docker itself. It uses a multi-stage Dockerfile to build and ship backstage. - -## Usage - -You can simply run the following command to build backstage. - -``` -# Make sure you are in the root directory of backstage then run -docker build -t backstage-frontend -f ./contrib/docker/multi-stage-frontend/Dockerfile . -``` - -After a successful build, You can simply run backstage frontend with the following command. - -``` -docker run -it --rm -p 3080:80 backstage-frontend -``` diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index ef178b2977..383cf0615a 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -232,7 +232,7 @@ package, which is done as follows: Once the `app-backend` is removed from the backend, you can use your favorite static file serving method for serving the frontend. An example of how to set up an NGINX image is available in the -[contrib folder in the main repo](https://github.com/backstage/backstage/blob/master/contrib/docker/frontend-with-nginx/Dockerfile) +[contrib folder in the main repo](https://github.com/backstage/backstage/blob/master/contrib/docker/frontend-with-nginx) Note that if you're building a separate docker build of the frontend you probably need to adjust `.dockerignore` appropriately. Most likely by making diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md index 5ce71ebfad..117471f749 100644 --- a/docs/features/techdocs/FAQ.md +++ b/docs/features/techdocs/FAQ.md @@ -45,3 +45,15 @@ metadata annotation is used in the build process of TechDocs. But when annotation should still be present in entity descriptor file (e.g. `catalog-info.yaml`) for Backstage to know that TechDocs is enabled for the entity. + +#### Is it possible for users to suggest changes or provide feedback on a TechDocs page? + +This is supported for TechDocs sites whose source code is hosted in either +GitHub or GitLab. In order to add "edit this page" and "leave feedback" buttons +on a TechDocs page, be sure that you have `repo_url` and `edit_uri` values in +your `mkdocs.yml` files per +[MkDocs instructions](https://www.mkdocs.org/user-guide/configuration). + +If the host name of your source code hosting URL does not include `github` or +`gitlab`, an `integrations` entry in your `app-config.yaml` pointed at your +source code provider is also needed (only the `host` key is necessary). diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md new file mode 100644 index 0000000000..50ebe722be --- /dev/null +++ b/docs/integrations/datadog-rum/installation.md @@ -0,0 +1,30 @@ +--- +id: installation +title: Datadog RUM Installation +sidebar_label: Installation +# prettier-ignore +description: Adding Datadog RUM to Your App +--- + +There is a basic [Datadog](https://docs.datadoghq.com/real_user_monitoring/) +integration built into Backstage. You can enable it by adding the following to +your app configuration: + +```yaml +app: + datadogRum: + clientToken: '123456789' + applicationId: qwerty + # site: # datadoghq.eu default = datadoghq.com + # env: # optional +``` + +Replace the clientToken and applicationId with the ones generated for you +Datadog. + +optional arguments: + +``` +site: datadoghq.eu # default equals datadoghq.com +env: dev # allow to specify the environment +``` diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index de6f4bf988..b5a6a0ba60 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -13,6 +13,11 @@ Backstage integrator. See how to install Google Analytics in your app [here](../integrations/google-analytics/installation.md) +## Datadog RUM Events + +See how to install Datadog Events in your app +[here](../integrations/datadog-rum/installation.md) + ## Logging The backend supplies a central [winston](https://github.com/winstonjs/winston) diff --git a/packages/app/public/index.html b/packages/app/public/index.html index 9a665141c0..da4d3cf3b8 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -49,8 +49,8 @@ <%= app.title %> - <% if (app.googleAnalyticsTrackingId && typeof app.googleAnalyticsTrackingId - === 'string') { %> + <% if (app.googleAnalyticsTrackingId && typeof + app.googleAnalyticsTrackingId==='string' ) { %> + <% } %> <% if (app.datadogRum.clientToken && app.datadogRum.applicationId ) + { %> + <% } %> +
diff --git a/packages/cli/package.json b/packages/cli/package.json index 548e659e76..251b2ab076 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -180,6 +180,36 @@ "UA-000000-0" ] }, + "datadogRum": { + "type": "object", + "description": "Datadog RUM events configuration", + "properties": { + "env": { + "type": "string", + "visibility": "frontend", + "description": "Environment for Datadog RUM events" + }, + "clientToken": { + "type": "string", + "visibility": "frontend", + "description": "clientToken for Datadog RUM events" + }, + "applicationId": { + "type": "string", + "visibility": "frontend", + "description": "applicationId for Datadog RUM events" + }, + "site": { + "type": "string", + "visibility": "frontend", + "description": "site for Datadog RUM events" + } + }, + "required": [ + "clientToken", + "applicationId" + ] + }, "listen": { "type": "object", "description": "Listening configuration for local development", diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index f81e055f69..8837554f22 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -127,6 +127,16 @@ export async function createConfig( googleAnalyticsTrackingId: frontendConfig.getOptionalString( 'app.googleAnalyticsTrackingId', ), + datadogRum: { + env: frontendConfig.getOptionalString('app.datadogRum.env'), + clientToken: frontendConfig.getOptionalString( + 'app.datadogRum.clientToken', + ), + applicationId: frontendConfig.getOptionalString( + 'app.datadogRum.applicationId', + ), + site: frontendConfig.getOptionalString('app.datadogRum.site'), + }, }, }, }), diff --git a/plugins/catalog/README.md b/plugins/catalog/README.md index e9405a41f5..611d2989e8 100644 --- a/plugins/catalog/README.md +++ b/plugins/catalog/README.md @@ -1,12 +1,25 @@ -# Catalog Frontend - -WORK IN PROGRESS +# Backstage Catalog Frontend This is the frontend part of the default catalog plugin. It will implement the core API for handling your catalog of software, and supply the base views to show and manage them. +## Getting Started + +This frontend plugin can be started in a standalone mode from directly in this package +with `yarn start`. However, it will have limited functionality and that process is +most convenient when developing the catalog frontend plugin itself. + +To evaluate the catalog and have a greater amount of functionality available, from the main +Backstage root folder, instead do: + +```bash +yarn dev +``` + +This will launch both frontend and backend in the same window, populated with some example entities. + ## Links - [Backend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend) diff --git a/plugins/catalog/src/components/CatalogPage/utils/locales/goodEvening.locales.json b/plugins/catalog/src/components/CatalogPage/utils/locales/goodEvening.locales.json index 890899fcf3..36d48eaf39 100644 --- a/plugins/catalog/src/components/CatalogPage/utils/locales/goodEvening.locales.json +++ b/plugins/catalog/src/components/CatalogPage/utils/locales/goodEvening.locales.json @@ -75,7 +75,7 @@ "Quiché": "Xe q’ij", "Romani [Sinte]": "Lashi rachi", "Romanian": "Bunã seara", - "Russian": "Dobry vyecher", + "Russian": "Добрый вечер", "Scottish Gaelic": "Feasgar mhath", "Scots": "Guid eenin", "Sesotho": "Fonane", @@ -91,7 +91,7 @@ "Telugu": "శుభ సాయంత్రం", "Thai": "Sawat-dii torn khum", "Turkish": "İyi akşamlar", - "Ukrainian": "Dobry vechir", + "Ukrainian": "Добрий вечiр", "Uzbek": "Xayrli kech", "Welsh": "Noswaith dda", "Yiddish": "Ah gutn ovnt", diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index feed758caa..ff4f47dca0 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { EntityName } from '@backstage/catalog-model'; -import { useApi } from '@backstage/core'; +import { useApi, configApiRef } from '@backstage/core'; import { BackstageTheme } from '@backstage/theme'; import { useTheme } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; @@ -31,6 +31,7 @@ import transformer, { rewriteDocLinks, sanitizeDOM, simplifyMkdocsFooter, + addGitFeedbackLink, } from '../transformers'; import { TechDocsNotFound } from './TechDocsNotFound'; import TechDocsProgressBar from './TechDocsProgressBar'; @@ -52,6 +53,7 @@ export const Reader = ({ entityId, onReady }: Props) => { const [loadedPath, setLoadedPath] = useState(''); const [atInitialLoad, setAtInitialLoad] = useState(true); const [newerDocsExist, setNewerDocsExist] = useState(false); + const configApi = useApi(configApiRef); const { value: isSynced, @@ -142,6 +144,7 @@ export const Reader = ({ entityId, onReady }: Props) => { rewriteDocLinks(), removeMkdocsHeader(), simplifyMkdocsFooter(), + addGitFeedbackLink(configApi), injectCss({ css: ` body { @@ -313,6 +316,7 @@ export const Reader = ({ entityId, onReady }: Props) => { name, newerDocsExist, isSynced, + configApi, ]); // docLoadError not considered an error state if sync request is still ongoing diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts new file mode 100644 index 0000000000..4c9ea04fe9 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createTestShadowDom } from '../../test-utils'; +import { addGitFeedbackLink } from './addGitFeedbackLink'; + +const configApi = { + getConfigArray: function getConfigArray(key: string) { + return key === 'integrations.github' + ? [{ data: { host: 'self-hosted-git-hub-provider.com' } }] + : []; + }, +}; + +describe('addGitFeedbackLink', () => { + it('adds a feedback link when a Gitlab source edit link is available', () => { + const shadowDom = createTestShadowDom( + ` + + +
+

HeaderText

+ +
+ + `, + { + preTransformers: [addGitFeedbackLink(configApi)], + postTransformers: [], + }, + ); + + expect(shadowDom.querySelector('#git-feedback-link')).toBeTruthy(); + expect( + (shadowDom.querySelector('#git-feedback-link') as HTMLLinkElement)!.href, + ).toEqual( + 'https://gitlab.com/reponame/username/issues/new?issue[title]=Documentation%20Feedback%3A%20HeaderText&issue[description]=Page%20source%3A%0Ahttps%3A%2F%2Fgitlab.com%2Freponame%2Fusername%2Fdocs%2FTestDoc.md%0A%0AFeedback%3A', + ); + }); + + it('adds a feedback link when a Github source edit link is available', () => { + const shadowDom = createTestShadowDom( + ` + + + + + `, + { + preTransformers: [addGitFeedbackLink(configApi)], + postTransformers: [], + }, + ); + + expect(shadowDom.querySelector('#git-feedback-link')).toBeTruthy(); + expect( + (shadowDom.querySelector('#git-feedback-link') as HTMLLinkElement)!.href, + ).toEqual( + 'https://github.com/reponame/username/issues/new?title=Documentation%20Feedback%3A%20HeaderText&body=Page%20source%3A%0Ahttps%3A%2F%2Fgithub.com%2Freponame%2Fusername%2Fdocs%2FTestDoc.md%0A%0AFeedback%3A', + ); + }); + + it('does not add a feedback link when no source edit link is available', () => { + const shadowDom = createTestShadowDom( + ` + + + + + `, + { + preTransformers: [addGitFeedbackLink(configApi)], + postTransformers: [], + }, + ); + + expect(shadowDom.querySelector('#git-feedback-link')).toBeFalsy(); + }); + + it('does not add a feedback link when a Gitlab or Github source edit link is not available', () => { + const shadowDom = createTestShadowDom( + ` + + + + + `, + { + preTransformers: [addGitFeedbackLink(configApi)], + postTransformers: [], + }, + ); + + expect(shadowDom.querySelector('#git-feedback-link')).toBeFalsy(); + }); + + it('adds a feedback link when a Gitlab or Github source edit link is not available but hostname matches an integrations host', () => { + const shadowDom = createTestShadowDom( + ` + + + + + `, + { + preTransformers: [addGitFeedbackLink(configApi)], + postTransformers: [], + }, + ); + + expect(shadowDom.querySelector('#git-feedback-link')).toBeTruthy(); + expect( + (shadowDom.querySelector('#git-feedback-link') as HTMLLinkElement)!.href, + ).toEqual( + 'https://self-hosted-git-hub-provider.com/reponame/username/issues/new?title=Documentation%20Feedback%3A%20HeaderText&body=Page%20source%3A%0Ahttps%3A%2F%2Fself-hosted-git-hub-provider.com%2Freponame%2Fusername%2Fdocs%2FTestDoc.md%0A%0AFeedback%3A', + ); + }); +}); diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts new file mode 100644 index 0000000000..d19d90228c --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Transformer } from './index'; +import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +// requires repo +export const addGitFeedbackLink = (configApi: any): Transformer => { + return dom => { + // attempting to use selectors that are more likely to be static as MkDocs updates over time + const sourceAnchor = dom.querySelector( + '[title="Edit this page"]', + ) as HTMLAnchorElement; + + // don't show if edit link not available in raw page + if (!sourceAnchor || !sourceAnchor.href) { + return dom; + } + let gitHost = ''; + + const sourceURL = new URL(sourceAnchor.href); + const githubHosts = configApi + .getConfigArray('integrations.github') + .map((integration: any) => integration.data.host); + const gitlabHosts = configApi + .getConfigArray('integrations.gitlab') + .map((integration: any) => integration.data.host); + + // don't show if can't identify edit link hostname as a gitlab/github hosting + if ( + githubHosts.includes(sourceURL.hostname) || + sourceURL.origin.includes('github') + ) { + gitHost = 'github'; + } else if ( + gitlabHosts.includes(sourceURL.hostname) || + sourceURL.origin.includes('gitlab') + ) { + gitHost = 'gitlab'; + } else { + return dom; + } + + // topmost h1 only contains title for whole page + const title = (dom.querySelector('article>h1') as HTMLElement).childNodes[0] + .textContent; + const issueTitle = encodeURIComponent(`Documentation Feedback: ${title}`); + const issueDesc = encodeURIComponent( + `Page source:\n${sourceAnchor.href}\n\nFeedback:`, + ); + const repoPath = sourceURL.pathname.split('/').slice(0, 3).join('/'); + + const feedbackLink = sourceAnchor.cloneNode() as HTMLAnchorElement; + switch (gitHost) { + case 'gitlab': + feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?issue[title]=${issueTitle}&issue[description]=${issueDesc}`; + break; + case 'github': + feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?title=${issueTitle}&body=${issueDesc}`; + break; + default: + return dom; + } + ReactDOM.render(React.createElement(FeedbackOutlinedIcon), feedbackLink); + feedbackLink.style.paddingLeft = '5px'; + feedbackLink.title = 'Leave feedback for this page'; + feedbackLink.id = 'git-feedback-link'; + sourceAnchor?.insertAdjacentElement('beforebegin', feedbackLink); + return dom; + }; +}; diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 4da4cb3add..a005ad470b 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -15,6 +15,7 @@ */ export * from './addBaseUrl'; +export * from './addGitFeedbackLink'; export * from './rewriteDocLinks'; export * from './addLinkClickListener'; export * from './removeMkdocsHeader';