diff options
author | Wouter Admiraal <45544358+wouter-admiraal-sonarsource@users.noreply.github.com> | 2019-08-12 08:39:46 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-08-12 20:21:13 +0200 |
commit | 21805ac1812e1db25ea1b8267593c6893b367fb4 (patch) | |
tree | f0c5c2bd3d8520f9727876f76658095d34c04794 | |
parent | 984396ddf2e5ad5b5b22fe62408f77462e85b17a (diff) | |
download | sonarqube-21805ac1812e1db25ea1b8267593c6893b367fb4.tar.gz sonarqube-21805ac1812e1db25ea1b8267593c6893b367fb4.zip |
SONAR-12325 Limit embedded documentation overrides to language plugins
4 files changed, 68 insertions, 32 deletions
diff --git a/server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts b/server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts index 4bcd5fae2ab..12bf2c890f8 100644 --- a/server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts +++ b/server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ParsedContent } from '../../../helpers/markdown'; import { mockDocumentationMarkdown } from '../../../helpers/testMocks'; jest.mock('remark', () => ({ @@ -86,10 +87,9 @@ it('should correctly handle overrides (replace & add)', () => { key: 'tata' }; - const overrides: string[] = [ - mockDocumentationMarkdown(overrideFooDoc), - mockDocumentationMarkdown(newDoc) - ]; + const overrides: T.Dict<ParsedContent> = {}; + overrides[foo.url] = { frontmatter: overrideFooDoc, content: overrideFooDoc.content }; + overrides[`analysis/languages/${newDoc.key}`] = { frontmatter: newDoc, content: newDoc.content }; const pages = getPages(overrides); expect(pages.length).toBe(3); @@ -99,7 +99,7 @@ it('should correctly handle overrides (replace & add)', () => { expect(pages[2].title).toBe(newDoc.title); }); -function getPages(overrides: string[] = []) { +function getPages(overrides: T.Dict<ParsedContent> = {}) { // This allows the use of out-of-scope data inside jest.mock // Usually, it is impossible as jest.mock'ed module is hoisted on the top of the file return require.requireActual('../pages').default(overrides); 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 1c4a430daf7..15c515ed206 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 @@ -33,7 +33,9 @@ import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import NotFound from '../../../app/components/NotFound'; import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock'; +import { ParsedContent, separateFrontMatter } from '../../../helpers/markdown'; import { isSonarCloud } from '../../../helpers/system'; +import { getUrlsList } from '../navTreeUtils'; import getPages from '../pages'; import '../styles.css'; import { DocumentationEntry } from '../utils'; @@ -49,6 +51,8 @@ interface State { tree: DocNavigationItem[]; } +const LANGUAGES_BASE_URL = 'analysis/languages'; + export default class App extends React.PureComponent<Props, State> { mounted = false; state: State = { @@ -67,7 +71,7 @@ export default class App extends React.PureComponent<Props, State> { ? ((navigationTreeSonarCloud as any).default as DocNavigationItem[]) : ((navigationTreeSonarQube as any).default as DocNavigationItem[]); - this.getLanguagesOverrides().then( + this.getLanguagePluginsDocumentation(tree).then( overrides => { if (this.mounted) { this.setState({ @@ -92,15 +96,13 @@ export default class App extends React.PureComponent<Props, State> { removeSideBarClass(); } - getLanguagesOverrides = () => { - const pluginStaticFileNameRegEx = new RegExp(`^static/(.*)`); - + getLanguagePluginsDocumentation = (tree: DocNavigationItem[]) => { return getInstalledPlugins() .then(plugins => Promise.all( plugins.map(plugin => { if (plugin.documentationPath) { - const matchArray = pluginStaticFileNameRegEx.exec(plugin.documentationPath); + const matchArray = /^static\/(.*)/.exec(plugin.documentationPath); if (matchArray && matchArray.length > 1) { // eslint-disable-next-line promise/no-nesting @@ -114,7 +116,29 @@ export default class App extends React.PureComponent<Props, State> { }) ) ) - .then(contents => contents.filter(isDefined)); + .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; + } + }); + + return parsedContent; + }); }; render() { 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 df65c2620ab..da36befcd1b 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 getPages from '../../pages'; import App from '../App'; jest.mock('../../../../components/common/ScreenPositionHelper', () => ({ @@ -90,19 +91,27 @@ jest.mock('sonar-ui-common/helpers/pages', () => ({ removeSideBarClass: jest.fn() })); -jest.mock('sonar-ui-common/helpers/request', () => ({ - request: jest.fn(() => ({ - submit: jest.fn().mockResolvedValue({ status: 200, text: jest.fn().mockReturnValue('TEST') }) - })) -})); +jest.mock('sonar-ui-common/helpers/request', () => { + const { mockDocumentationMarkdown } = require.requireActual('../../../../helpers/testMocks'); + return { + request: jest.fn(() => ({ + submit: jest.fn().mockResolvedValue({ + status: 200, + text: jest.fn().mockResolvedValue(mockDocumentationMarkdown({ key: 'csharp' })) + }) + })) + }; +}); jest.mock('../../pages', () => { const { mockDocumentationEntry } = require.requireActual('../../../../helpers/testMocks'); return { - default: () => [ - mockDocumentationEntry(), - mockDocumentationEntry({ url: '/analysis/languages/csharp/' }) - ] + default: jest + .fn() + .mockReturnValue([ + mockDocumentationEntry(), + mockDocumentationEntry({ url: '/analysis/languages/csharp/' }) + ]) }; }); @@ -116,6 +125,10 @@ jest.mock('../../../../api/plugins', () => ({ ]) })); +beforeEach(() => { + jest.clearAllMocks(); +}); + it('should render correctly for SonarQube', async () => { const wrapper = shallowRender(); expect(wrapper.find('DeferredSpinner').exists()).toBe(true); @@ -143,12 +156,19 @@ it("should show a 404 if the page doesn't exist", async () => { }); it('should try to fetch language plugin documentation if documentationPath matches', async () => { + (isSonarCloud as jest.Mock).mockReturnValue(false); + const wrapper = shallowRender(); await waitAndUpdate(wrapper); expect(request).toHaveBeenCalledWith('/static/csharp/documentation.md'); expect(request).not.toHaveBeenCalledWith('/static/vbnet/documentation.md'); expect(request).not.toHaveBeenCalledWith('/static/vbnett/documentation.md'); + expect(getPages).toHaveBeenCalledWith( + expect.objectContaining({ + 'analysis/languages/csharp': expect.any(Object) + }) + ); }); function shallowRender(props: Partial<App['props']> = {}) { diff --git a/server/sonar-web/src/main/js/apps/documentation/pages.ts b/server/sonar-web/src/main/js/apps/documentation/pages.ts index 15ded856918..e38a56da341 100644 --- a/server/sonar-web/src/main/js/apps/documentation/pages.ts +++ b/server/sonar-web/src/main/js/apps/documentation/pages.ts @@ -23,18 +23,10 @@ import { filterContent, ParsedContent, separateFrontMatter } from '../../helpers import * as Docs from './documentation.directory-loader'; import { DocumentationEntry, DocumentationEntryScope } from './utils'; -const LANGUAGES_BASE_URL = 'analysis/languages'; - -export default function getPages(overrides: string[] = []): DocumentationEntry[] { - const parsedOverrides: T.Dict<ParsedContent> = {}; - overrides.forEach(override => { - const parsedOverride = separateFrontMatter(override); - if (parsedOverride && parsedOverride.frontmatter && parsedOverride.frontmatter.key) { - parsedOverrides[`${LANGUAGES_BASE_URL}/${parsedOverride.frontmatter.key}`] = parsedOverride; - } - }); - - // Merge with existing entries. +export default function getPages( + parsedOverrides: T.Dict<ParsedContent> = {} +): DocumentationEntry[] { + // Get entries, merge with overrides if applicable. const pages = ((Docs as unknown) as Array<{ content: string; path: string }>).map(file => { let parsed = separateFrontMatter(file.content); |