]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13689 Add issue tracker url to languages embedded documentation page
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Mon, 7 Sep 2020 11:00:06 +0000 (13:00 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 18 Sep 2020 20:07:15 +0000 (20:07 +0000)
server/sonar-web/src/main/js/apps/documentation/components/App.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/types/plugins.ts

index 3ad204c15a20174eb2227beefd63823d085a95f2..dab74273954651ad0ea48688ef0293963a2d3383 100644 (file)
@@ -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<Props, State> {
     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<ParsedContent> = {};
-
-        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<ParsedContent> = {};
+
+    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<Props, State> {
       );
     }
 
-    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'
index 2c34d10119ae13214756ab9a411381b1c158eb42..ea416cd08ca9344fe2221a70a25eb48b55f46d06 100644 (file)
@@ -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<App['props']> = {}) {
   return shallow(<App params={{ splat: 'lorem/ipsum' }} {...props} />);
 }
index 27a5ce4d37cd18c81afd2c223a77c5585846e010..648b792103af63bb9a8fb1d6581d4eea1b09239c 100644 (file)
@@ -50,6 +50,7 @@ export interface PendingPlugin extends Plugin {
 
 export interface InstalledPlugin extends PendingPlugin {
   documentationPath?: string;
+  issueTrackerUrl?: string;
   filename: string;
   hash: string;
   sonarLintSupported: boolean;