Browse Source

SONAR-12325 Limit embedded documentation overrides to language plugins

tags/8.0
Wouter Admiraal 4 years ago
parent
commit
21805ac181

+ 5
- 5
server/sonar-web/src/main/js/apps/documentation/__tests__/pages-test.ts View File

@@ -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);

+ 30
- 6
server/sonar-web/src/main/js/apps/documentation/components/App.tsx View File

@@ -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() {

+ 29
- 9
server/sonar-web/src/main/js/apps/documentation/components/__tests__/App-test.tsx View 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 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']> = {}) {

+ 4
- 12
server/sonar-web/src/main/js/apps/documentation/pages.ts View File

@@ -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);


Loading…
Cancel
Save