From 1b7e5b30a5695d613d5c2f5942985f52ac76100e Mon Sep 17 00:00:00 2001 From: Philippe Perrin Date: Mon, 7 Sep 2020 13:00:06 +0200 Subject: [PATCH] SONAR-13689 Add issue tracker url to languages embedded documentation page --- .../js/apps/documentation/components/App.tsx | 92 ++++++++++--------- .../components/__tests__/App-test.tsx | 20 +++- server/sonar-web/src/main/js/types/plugins.ts | 1 + 3 files changed, 68 insertions(+), 45 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx b/server/sonar-web/src/main/js/apps/documentation/components/App.tsx index 3ad204c15a2..dab74273954 100644 --- a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/documentation/components/App.tsx @@ -35,7 +35,7 @@ import ScreenPositionHelper from '../../../components/common/ScreenPositionHelpe import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock'; import { ParsedContent, separateFrontMatter } from '../../../helpers/markdown'; import { isSonarCloud } from '../../../helpers/system'; -import { PluginType } from '../../../types/plugins'; +import { InstalledPlugin, PluginType } from '../../../types/plugins'; import { getUrlsList } from '../navTreeUtils'; import getPages from '../pages'; import '../styles.css'; @@ -97,49 +97,55 @@ export default class App extends React.PureComponent { removeSideBarClass(); } - getLanguagePluginsDocumentation = (tree: DocNavigationItem[]) => { - return getInstalledPlugins(PluginType.Bundled) - .then(plugins => - Promise.all( - plugins.map(plugin => { - if (plugin.documentationPath) { - const matchArray = /^static\/(.*)/.exec(plugin.documentationPath); - - if (matchArray && matchArray.length > 1) { - // eslint-disable-next-line promise/no-nesting - return getPluginStaticFileContent(plugin.key, matchArray[1]).then( - content => content, - () => undefined - ); - } - } - return undefined; - }) - ) - ) - .then(contents => contents.filter(isDefined)) - .then(contents => { - const regex = new RegExp(`/${LANGUAGES_BASE_URL}/\\w+/$`); - const overridablePaths = getUrlsList(tree).filter( - path => regex.test(path) && path !== `/${LANGUAGES_BASE_URL}/overview/` - ); - - const parsedContent: T.Dict = {}; - - contents.forEach(content => { - const parsed = separateFrontMatter(content); - if ( - parsed && - parsed.frontmatter && - parsed.frontmatter.key && - overridablePaths.includes(`/${LANGUAGES_BASE_URL}/${parsed.frontmatter.key}/`) - ) { - parsedContent[`${LANGUAGES_BASE_URL}/${parsed.frontmatter.key}`] = parsed; + getLanguagePluginsDocumentation = async (tree: DocNavigationItem[]) => { + const plugins = await getInstalledPlugins(PluginType.Bundled).catch( + () => [] as InstalledPlugin[] + ); + + const pluginsWithDoc = await Promise.all( + plugins.map(plugin => { + if (plugin.documentationPath) { + const matchArray = /^static\/(.*)/.exec(plugin.documentationPath); + + if (matchArray && matchArray.length > 1) { + return getPluginStaticFileContent(plugin.key, matchArray[1]).then( + content => ({ ...plugin, content }), + () => undefined + ); } - }); + } + + return undefined; + }) + ); + + const regex = new RegExp(`/${LANGUAGES_BASE_URL}/\\w+/$`); + const overridablePaths = getUrlsList(tree).filter( + path => regex.test(path) && path !== `/${LANGUAGES_BASE_URL}/overview/` + ); + + const parsedContent: T.Dict = {}; + + pluginsWithDoc.filter(isDefined).forEach(plugin => { + const parsed = separateFrontMatter(plugin.content); + + if (plugin.issueTrackerUrl) { + // Inject issue tracker link + let issueTrackerLink = '## Issue Tracker'; + issueTrackerLink += '\r\n'; + issueTrackerLink += `Check the [issue tracker](${plugin.issueTrackerUrl}) for this language.`; + parsed.content = `${parsed.content}\r\n${issueTrackerLink}`; + } + + if ( + parsed?.frontmatter?.key && + overridablePaths.includes(`/${LANGUAGES_BASE_URL}/${parsed.frontmatter.key}/`) + ) { + parsedContent[`${LANGUAGES_BASE_URL}/${parsed.frontmatter.key}`] = parsed; + } + }); - return parsedContent; - }); + return parsedContent; }; render() { @@ -154,7 +160,7 @@ export default class App extends React.PureComponent { ); } - const page = pages.find(p => p.url === '/' + splat); + const page = pages.find(p => p.url === `/${splat}`); const mainTitle = translate( 'documentation.page_title', isSonarCloud() ? 'sonarcloud' : 'sonarqube' diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx index 2c34d10119a..ea416cd08ca 100644 --- a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx @@ -23,6 +23,7 @@ import { addSideBarClass, removeSideBarClass } from 'sonar-ui-common/helpers/pag import { request } from 'sonar-ui-common/helpers/request'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { isSonarCloud } from '../../../../helpers/system'; +import { InstalledPlugin } from '../../../../types/plugins'; import getPages from '../../pages'; import App from '../App'; @@ -108,10 +109,14 @@ jest.mock('../../pages', () => { jest.mock('../../../../api/plugins', () => ({ getInstalledPlugins: jest.fn().mockResolvedValue([ - { key: 'csharp', documentationPath: 'static/documentation.md' }, + { + key: 'csharp', + documentationPath: 'static/documentation.md', + issueTrackerUrl: 'csharp_plugin_issue_tracker_url' + }, { key: 'vbnet', documentationPath: 'Sstatic/documentation.md' }, { key: 'vbnett', documentationPath: undefined } - ]) + ] as InstalledPlugin[]) })); beforeEach(() => { @@ -160,6 +165,17 @@ it('should try to fetch language plugin documentation if documentationPath match ); }); +it('should display the issue tracker url of the plugin if it exists', async () => { + (isSonarCloud as jest.Mock).mockReturnValue(false); + + const wrapper = shallowRender({ params: { splat: 'analysis/languages/csharp/' } }); + await waitAndUpdate(wrapper); + + const { content } = (getPages as jest.Mock).mock.calls[0][0]['analysis/languages/csharp']; + + expect(content).toContain('csharp_plugin_issue_tracker_url'); +}); + function shallowRender(props: Partial = {}) { return shallow(); } diff --git a/server/sonar-web/src/main/js/types/plugins.ts b/server/sonar-web/src/main/js/types/plugins.ts index 27a5ce4d37c..648b792103a 100644 --- a/server/sonar-web/src/main/js/types/plugins.ts +++ b/server/sonar-web/src/main/js/types/plugins.ts @@ -50,6 +50,7 @@ export interface PendingPlugin extends Plugin { export interface InstalledPlugin extends PendingPlugin { documentationPath?: string; + issueTrackerUrl?: string; filename: string; hash: string; sonarLintSupported: boolean; -- 2.39.5