Merge pull request #5159 from adrianke77/techdocs-add-git-feedback-link

Add Gitlab/Github feedback link icon in Techdocs Reader
This commit is contained in:
Eric Peterson
2021-04-20 14:22:53 +02:00
committed by GitHub
6 changed files with 253 additions and 1 deletions
+7
View File
@@ -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.
+12
View File
@@ -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).
@@ -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
@@ -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(
`
<!DOCTYPE html>
<html>
<article class="md-content__inner">
<h1>HeaderText</h1>
<a title="Edit this page" href="https://gitlab.com/reponame/username/docs/TestDoc.md"></>
</article>
</html>
`,
{
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(
`
<!DOCTYPE html>
<html>
<article class="md-content__inner">
<h1>HeaderText</h1>
<a title="Edit this page" href="https://github.com/reponame/username/docs/TestDoc.md"></>
</article>
</html>
`,
{
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(
`
<!DOCTYPE html>
<html>
<article class="md-content__inner">
<h1>HeaderText<a class="headerlink" href="http://headerlink.com"></a></h1>
</article>
</html>
`,
{
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(
`
<!DOCTYPE html>
<html>
<article class="md-content__inner">
<h1>HeaderText<a class="headerlink" href="http://headerlink.com"></a></h1>
<a title="Edit this page" href="https://not-a-git-provider.com/reponame/username/docs/TestDoc.md"/>
</article>
</html>
`,
{
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(
`
<!DOCTYPE html>
<html>
<article class="md-content__inner">
<h1>HeaderText<a class="headerlink" href="http://headerlink.com"></a></h1>
<a title="Edit this page" href="https://self-hosted-git-hub-provider.com/reponame/username/docs/TestDoc.md"/>
</article>
</html>
`,
{
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',
);
});
});
@@ -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;
};
};
@@ -15,6 +15,7 @@
*/
export * from './addBaseUrl';
export * from './addGitFeedbackLink';
export * from './rewriteDocLinks';
export * from './addLinkClickListener';
export * from './removeMkdocsHeader';