aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/api
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/api')
-rw-r--r--server/sonar-web/src/main/js/api/components.ts2
-rw-r--r--server/sonar-web/src/main/js/api/mocks/CodeServiceMocks.ts168
-rw-r--r--server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts631
-rw-r--r--server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts108
-rw-r--r--server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts286
5 files changed, 676 insertions, 519 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts
index 5ed1fc90d08..aa9c228712d 100644
--- a/server/sonar-web/src/main/js/api/components.ts
+++ b/server/sonar-web/src/main/js/api/components.ts
@@ -140,7 +140,7 @@ export function getComponent(
return getJSON('/api/measures/component', data);
}
-type GetTreeParams = {
+export type GetTreeParams = {
asc?: boolean;
component: string;
p?: number;
diff --git a/server/sonar-web/src/main/js/api/mocks/CodeServiceMocks.ts b/server/sonar-web/src/main/js/api/mocks/CodeServiceMocks.ts
deleted file mode 100644
index 2c9a8021110..00000000000
--- a/server/sonar-web/src/main/js/api/mocks/CodeServiceMocks.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { cloneDeep, flatMap, map } from 'lodash';
-import { mockComponent, mockComponentMeasure } from '../../helpers/mocks/component';
-import { ComponentQualifier } from '../../types/component';
-import { RawIssuesResponse } from '../../types/issues';
-import { ComponentMeasure, Metric, Paging } from '../../types/types';
-import { getChildren, getComponentTree } from '../components';
-import { searchIssues } from '../issues';
-
-interface ComponentTree {
- component: ComponentMeasure;
- child: ComponentTree[];
-}
-
-function isLeaf(node: ComponentTree) {
- return node.child.length === 0;
-}
-
-function listChildComponent(node: ComponentTree): ComponentMeasure[] {
- return map(node.child, (n) => n.component);
-}
-
-function listAllComponent(node: ComponentTree): ComponentMeasure[] {
- if (isLeaf(node)) {
- return [node.component];
- }
-
- return [node.component, ...flatMap(node.child, listAllComponent)];
-}
-
-function listLeavesComponent(node: ComponentTree): ComponentMeasure[] {
- if (isLeaf(node)) {
- return [node.component];
- }
- return flatMap(node.child, listLeavesComponent);
-}
-
-export default class CodeServiceMock {
- componentTree: ComponentTree;
-
- constructor() {
- this.componentTree = {
- component: mockComponentMeasure(),
- child: [
- {
- component: mockComponentMeasure(false, {
- key: 'foo:folerA',
- name: 'folderA',
- path: 'folderA',
- qualifier: ComponentQualifier.Directory,
- }),
- child: [
- {
- component: mockComponentMeasure(true, {
- key: 'foo:folderA/out.tsx',
- name: 'out.tsx',
- path: 'folderA/out.tsx',
- }),
- child: [],
- },
- ],
- },
- {
- component: mockComponentMeasure(true, {
- key: 'foo:index.tsx',
- name: 'index.tsx',
- path: 'index.tsx',
- }),
- child: [],
- },
- ],
- };
- (getComponentTree as jest.Mock).mockImplementation(this.handleGetComponentTree);
- (getChildren as jest.Mock).mockImplementation(this.handleGetChildren);
- (searchIssues as jest.Mock).mockImplementation(this.handleSearchIssue);
- }
-
- findBaseComponent(key: string, from = this.componentTree): ComponentTree | undefined {
- if (from.component.key === key) {
- return from;
- }
- return from.child.find((node) => this.findBaseComponent(key, node));
- }
-
- handleGetChildren = (
- component: string
- ): Promise<{
- baseComponent: ComponentMeasure;
- components: ComponentMeasure[];
- metrics: Metric[];
- paging: Paging;
- }> => {
- return this.handleGetComponentTree('children', component);
- };
-
- handleGetComponentTree = (
- strategy: string,
- component: string
- ): Promise<{
- baseComponent: ComponentMeasure;
- components: ComponentMeasure[];
- metrics: Metric[];
- paging: Paging;
- }> => {
- const base = this.findBaseComponent(component);
- let components: ComponentMeasure[] = [];
- if (base === undefined) {
- return Promise.reject({
- errors: [{ msg: `No component has been found for id ${component}` }],
- });
- }
- if (strategy === 'all' || strategy === '') {
- components = listAllComponent(base);
- } else if (strategy === 'children') {
- components = listChildComponent(base);
- } else if (strategy === 'leaves') {
- components = listLeavesComponent(base);
- }
-
- return this.reply({
- baseComponent: base.component,
- components,
- metrics: [],
- paging: {
- pageIndex: 1,
- pageSize: 100,
- total: components.length,
- },
- });
- };
-
- handleSearchIssue = (): Promise<RawIssuesResponse> => {
- return this.reply({
- components: [],
- effortTotal: 1,
- facets: [],
- issues: [],
- languages: [],
- paging: { total: 0, pageIndex: 1, pageSize: 100 },
- });
- };
-
- getRootComponent() {
- return mockComponent(this.componentTree.component);
- }
-
- reply<T>(response: T): Promise<T> {
- return Promise.resolve(cloneDeep(response));
- }
-}
diff --git a/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts
new file mode 100644
index 00000000000..8308107d00f
--- /dev/null
+++ b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts
@@ -0,0 +1,631 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { cloneDeep, flatMap, map, pick, times } from 'lodash';
+import { mockComponent } from '../../helpers/mocks/component';
+import {
+ mockDuplicatedFile,
+ mockDuplication,
+ mockDuplicationBlock,
+ mockSourceLine,
+ mockSourceViewerFile,
+} from '../../helpers/mocks/sources';
+import { HttpStatus, RequestData } from '../../helpers/request';
+import { BranchParameters } from '../../types/branch-like';
+import { ComponentQualifier, TreeComponent, Visibility } from '../../types/component';
+import {
+ Component,
+ ComponentMeasure,
+ Dict,
+ DuplicatedFile,
+ Duplication,
+ Measure,
+ Metric,
+ Paging,
+ SourceLine,
+ SourceViewerFile,
+} from '../../types/types';
+import {
+ getChildren,
+ getComponentData,
+ getComponentForSourceViewer,
+ getComponentTree,
+ getDuplications,
+ getSources,
+ getTree,
+ GetTreeParams,
+} from '../components';
+
+interface ComponentTree {
+ component: Component;
+ ancestors: Component[];
+ measures?: Measure[];
+ children: ComponentTree[];
+}
+
+interface SourceFile {
+ component: SourceViewerFile;
+ lines: SourceLine[];
+ duplication?: {
+ duplications: Duplication[];
+ files: Dict<DuplicatedFile>;
+ };
+}
+
+function isLeaf(node: ComponentTree) {
+ return node.children.length === 0;
+}
+
+function listChildComponent(node: ComponentTree): Component[] {
+ return map(node.children, (n) => n.component);
+}
+
+function listAllComponent(node: ComponentTree): Component[] {
+ if (isLeaf(node)) {
+ return [node.component];
+ }
+
+ return [node.component, ...flatMap(node.children, listAllComponent)];
+}
+
+function listLeavesComponent(node: ComponentTree): Component[] {
+ if (isLeaf(node)) {
+ return [node.component];
+ }
+ return flatMap(node.children, listLeavesComponent);
+}
+
+export default class ComponentsServiceMock {
+ failLoadingComponentStatus: HttpStatus | undefined = undefined;
+ defaultComponents: ComponentTree[];
+ components: ComponentTree[];
+ defaultSourceFiles: SourceFile[];
+ sourceFiles: SourceFile[];
+
+ constructor(components?: ComponentTree[], sourceFiles?: SourceFile[]) {
+ const baseComponent = mockComponent({
+ key: 'foo',
+ name: 'Foo',
+ breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: ComponentQualifier.Project }],
+ });
+ const folderComponent = mockComponent({
+ key: 'foo:folderA',
+ name: 'folderA',
+ path: 'folderA',
+ qualifier: ComponentQualifier.Directory,
+ breadcrumbs: [
+ ...baseComponent.breadcrumbs,
+ { key: 'foo:folderA', name: 'folderA', qualifier: ComponentQualifier.Directory },
+ ],
+ });
+ this.defaultComponents = components || [
+ {
+ component: baseComponent,
+ ancestors: [],
+ children: [
+ {
+ component: folderComponent,
+ ancestors: [baseComponent],
+ children: [
+ {
+ component: mockComponent({
+ key: 'foo:folderA/out.tsx',
+ name: 'out.tsx',
+ path: 'folderA/out.tsx',
+ qualifier: ComponentQualifier.File,
+ breadcrumbs: [
+ ...folderComponent.breadcrumbs,
+ {
+ key: 'foo:folderA/out.tsx',
+ name: 'out.tsx',
+ qualifier: ComponentQualifier.File,
+ },
+ ],
+ }),
+ ancestors: [baseComponent, folderComponent],
+ children: [],
+ },
+ ],
+ },
+ {
+ component: mockComponent({
+ key: 'foo:index.tsx',
+ name: 'index.tsx',
+ path: 'index.tsx',
+ qualifier: ComponentQualifier.File,
+ breadcrumbs: [
+ ...baseComponent.breadcrumbs,
+ { key: 'foo:index.tsx', name: 'index.tsx', qualifier: ComponentQualifier.File },
+ ],
+ }),
+ ancestors: [baseComponent],
+ children: [],
+ },
+ {
+ component: mockComponent({
+ key: 'foo:test1.js',
+ name: 'test1.js',
+ path: 'test1.js',
+ qualifier: ComponentQualifier.File,
+ breadcrumbs: [
+ ...baseComponent.breadcrumbs,
+ { key: 'foo:test1.js', name: 'test1.js', qualifier: ComponentQualifier.File },
+ ],
+ }),
+ ancestors: [baseComponent],
+ children: [],
+ },
+ {
+ component: mockComponent({
+ key: 'foo:test2.js',
+ name: 'test2.js',
+ path: 'test2.js',
+ qualifier: ComponentQualifier.File,
+ breadcrumbs: [
+ ...baseComponent.breadcrumbs,
+ { key: 'foo:test2.js', name: 'test2.js', qualifier: ComponentQualifier.File },
+ ],
+ }),
+ ancestors: [baseComponent],
+ children: [],
+ },
+ {
+ component: mockComponent({
+ key: 'foo:testSymb.tsx',
+ name: 'testSymb.tsx',
+ path: 'testSymb.tsx',
+ qualifier: ComponentQualifier.File,
+ breadcrumbs: [
+ ...baseComponent.breadcrumbs,
+ {
+ key: 'foo:testSymb.tsx',
+ name: 'testSymb.tsx',
+ qualifier: ComponentQualifier.File,
+ },
+ ],
+ }),
+ ancestors: [baseComponent],
+ children: [],
+ },
+ {
+ component: mockComponent({
+ key: 'foo:empty.js',
+ name: 'empty.js',
+ path: 'empty.js',
+ qualifier: ComponentQualifier.File,
+ breadcrumbs: [
+ ...baseComponent.breadcrumbs,
+ { key: 'foo:empty.js', name: 'empty.js', qualifier: ComponentQualifier.File },
+ ],
+ }),
+ ancestors: [baseComponent],
+ children: [],
+ },
+ {
+ component: mockComponent({
+ key: 'foo:huge.js',
+ name: 'huge.js',
+ path: 'huge.js',
+ qualifier: ComponentQualifier.File,
+ breadcrumbs: [
+ ...baseComponent.breadcrumbs,
+ { key: 'foo:huge.js', name: 'huge.js', qualifier: ComponentQualifier.File },
+ ],
+ }),
+ ancestors: [baseComponent],
+ children: [],
+ },
+ ],
+ },
+ ];
+
+ this.defaultSourceFiles =
+ sourceFiles ||
+ ([
+ {
+ component: mockSourceViewerFile('index.tsx', 'foo'),
+ lines: times(50, (n) =>
+ mockSourceLine({
+ line: n,
+ code: 'function Test() {}',
+ })
+ ),
+ },
+ {
+ component: mockSourceViewerFile('folderA/out.tsx', 'foo'),
+ lines: times(50, (n) =>
+ mockSourceLine({
+ line: n,
+ code: 'function Test() {}',
+ })
+ ),
+ },
+ {
+ component: mockSourceViewerFile('test1.js', 'foo'),
+ lines: [
+ {
+ line: 1,
+ code: '\u003cspan class\u003d"cd"\u003e/*\u003c/span\u003e',
+ scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+ scmAuthor: 'stas.vilchik@sonarsource.com',
+ scmDate: '2018-07-10T20:21:20+0200',
+ duplicated: false,
+ isNew: false,
+ lineHits: 1,
+ coveredConditions: 1,
+ },
+ {
+ line: 2,
+ code: '\u003cspan class\u003d"cd"\u003e * SonarQube\u003c/span\u003e',
+ scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+ scmAuthor: 'stas.vilchik@sonarsource.com',
+ scmDate: '2018-07-10T20:21:20+0200',
+ duplicated: false,
+ isNew: false,
+ lineHits: 0,
+ conditions: 1,
+ },
+ {
+ line: 3,
+ code: '\u003cspan class\u003d"cd"\u003e * Copyright\u003c/span\u003e',
+ scmRevision: '89a3d21bc28f2fa6201b5e8b1185d5358481b3dd',
+ scmAuthor: 'pierre.guillot@sonarsource.com',
+ scmDate: '2022-01-28T21:03:07+0100',
+ duplicated: false,
+ isNew: false,
+ lineHits: 1,
+ },
+ {
+ line: 4,
+ code: '\u003cspan class\u003d"cd"\u003e * mailto:info AT sonarsource DOT com\u003c/span\u003e',
+ scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+ scmAuthor: 'stas.vilchik@sonarsource.com',
+ duplicated: false,
+ isNew: false,
+ lineHits: 1,
+ conditions: 1,
+ coveredConditions: 1,
+ },
+ {
+ line: 5,
+ code: '\u003cspan class\u003d"cd"\u003e * 5\u003c/span\u003e',
+ scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
+ duplicated: false,
+ isNew: false,
+ lineHits: 2,
+ conditions: 2,
+ coveredConditions: 1,
+ },
+ {
+ line: 6,
+ code: '\u003cspan class\u003d"cd"\u003e * 6\u003c/span\u003e',
+ scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
+ duplicated: false,
+ isNew: false,
+ lineHits: 0,
+ },
+ {
+ line: 7,
+ code: '\u003cspan class\u003d"cd"\u003e * 7\u003c/span\u003e',
+ scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
+ duplicated: true,
+ isNew: true,
+ },
+ {
+ code: '\u003cspan class\u003d"cd"\u003e * This program is free software; you can redistribute it and/or\u003c/span\u003e',
+ scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+ scmAuthor: 'stas.vilchik@sonarsource.com',
+ scmDate: '2018-07-10T20:21:20+0200',
+ duplicated: false,
+ isNew: false,
+ },
+ ],
+ duplication: {
+ duplications: [
+ mockDuplication({
+ blocks: [
+ mockDuplicationBlock({ from: 7, size: 1, _ref: '1' }),
+ mockDuplicationBlock({ from: 1, size: 1, _ref: '2' }),
+ ],
+ }),
+ ],
+ files: {
+ '1': mockDuplicatedFile({ key: 'foo:test1.js' }),
+ '2': mockDuplicatedFile({ key: 'foo:test2.js' }),
+ },
+ },
+ },
+ {
+ component: mockSourceViewerFile('test2.js', 'foo'),
+ lines: times(50, (n) =>
+ mockSourceLine({
+ line: n,
+ code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`,
+ })
+ ),
+ },
+ {
+ component: mockSourceViewerFile('empty.js', 'foo'),
+ lines: [],
+ },
+ {
+ component: mockSourceViewerFile('huge.js', 'foo'),
+ lines: times(200, (n) =>
+ mockSourceLine({
+ line: n,
+ code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`,
+ })
+ ),
+ },
+ {
+ component: mockSourceViewerFile('testSymb.tsx', 'foo'),
+ lines: times(20, (n) =>
+ mockSourceLine({
+ line: n,
+ code: ' <span class="sym-35 sym">symbole</span>',
+ })
+ ),
+ },
+ ] as SourceFile[]);
+
+ this.components = cloneDeep(this.defaultComponents);
+ this.sourceFiles = cloneDeep(this.defaultSourceFiles);
+
+ (getComponentTree as jest.Mock).mockImplementation(this.handleGetComponentTree);
+ (getChildren as jest.Mock).mockImplementation(this.handleGetChildren);
+ (getTree as jest.Mock).mockImplementation(this.handleGetTree);
+ (getComponentData as jest.Mock).mockImplementation(this.handleGetComponentData);
+ (getComponentForSourceViewer as jest.Mock).mockImplementation(
+ this.handleGetComponentForSourceViewer
+ );
+ (getDuplications as jest.Mock).mockImplementation(this.handleGetDuplications);
+ (getSources as jest.Mock).mockImplementation(this.handleGetSources);
+ }
+
+ findComponentTree = (key: string, from?: ComponentTree): ComponentTree | undefined => {
+ const recurse = (node: ComponentTree): ComponentTree | undefined => {
+ if (node.component.key === key) {
+ return node;
+ }
+ return node.children.find((child) => recurse(child));
+ };
+
+ if (from === undefined) {
+ for (let i = 0, len = this.components.length; i < len; i++) {
+ const tree = recurse(this.components[i]);
+ if (tree) {
+ return tree;
+ }
+ }
+ throw new Error(`Couldn't find component tree for key ${key}`);
+ }
+
+ return recurse(from);
+ };
+
+ findSourceFile = (key: string): SourceFile => {
+ const sourceFile = this.sourceFiles.find((s) => s.component.key === key);
+ if (sourceFile) {
+ return sourceFile;
+ }
+ throw new Error(`Couldn't find source file for key ${key}`);
+ };
+
+ registerComponent = (component: Component, ancestors: Component[] = []) => {
+ this.components.push({ component, ancestors, children: [] });
+ };
+
+ registerComponentTree = (componentTree: ComponentTree, replace = true) => {
+ if (replace) {
+ this.components = [];
+ }
+ this.components.push(componentTree);
+ };
+
+ setFailLoadingComponentStatus = (status: HttpStatus.Forbidden | HttpStatus.NotFound) => {
+ this.failLoadingComponentStatus = status;
+ };
+
+ getHugeFileKey = () => {
+ const { sourceFile } = this.sourceFiles.reduce(
+ (acc, sourceFile) => {
+ if (sourceFile.lines.length > acc.size) {
+ return {
+ sourceFile,
+ size: sourceFile.lines.length,
+ };
+ }
+ return acc;
+ },
+ { sourceFile: undefined, size: -Infinity }
+ );
+ if (sourceFile) {
+ return sourceFile.component.key;
+ }
+ throw new Error('Could not find a large source file');
+ };
+
+ getEmptyFileKey = () => {
+ const sourceFile = this.sourceFiles.find((sourceFile) => {
+ if (sourceFile.lines.length === 0) {
+ return sourceFile;
+ }
+ });
+ if (sourceFile) {
+ return sourceFile.component.key;
+ }
+ throw new Error('Could not find an empty source file');
+ };
+
+ getNonEmptyFileKey = (preferredKey = 'foo:test1.js') => {
+ let sourceFile = this.sourceFiles.find((sourceFile) => {
+ if (sourceFile.component.key === preferredKey) {
+ return sourceFile;
+ }
+ });
+
+ if (!sourceFile) {
+ sourceFile = this.sourceFiles.find((sourceFile) => {
+ if (sourceFile.lines.length > 0) {
+ return sourceFile;
+ }
+ });
+ }
+
+ if (sourceFile) {
+ return sourceFile.component.key;
+ }
+ throw new Error('Could not find a non-empty source file');
+ };
+
+ reset = () => {
+ this.components = cloneDeep(this.defaultComponents);
+ this.sourceFiles = cloneDeep(this.defaultSourceFiles);
+ };
+
+ handleGetChildren = (
+ component: string,
+ metrics: string[] = [],
+ data: RequestData = {}
+ ): Promise<{
+ baseComponent: ComponentMeasure;
+ components: ComponentMeasure[];
+ metrics: Metric[];
+ paging: Paging;
+ }> => {
+ return this.handleGetComponentTree('children', component, metrics, data);
+ };
+
+ handleGetComponentTree = (
+ strategy: string,
+ key: string,
+ _metrics: string[] = [],
+ { p = 1, ps = 100 }: RequestData = {}
+ ): Promise<{
+ baseComponent: ComponentMeasure;
+ components: ComponentMeasure[];
+ metrics: Metric[];
+ paging: Paging;
+ }> => {
+ const base = this.findComponentTree(key);
+ let components: Component[] = [];
+ if (base === undefined) {
+ return Promise.reject({
+ errors: [{ msg: `No component has been found for id ${key}` }],
+ });
+ }
+ if (strategy === 'all' || strategy === '') {
+ components = listAllComponent(base);
+ } else if (strategy === 'children') {
+ components = listChildComponent(base);
+ } else if (strategy === 'leaves') {
+ components = listLeavesComponent(base);
+ }
+
+ const componentsMeasures: ComponentMeasure[] = components.map((c) => {
+ return {
+ measures: this.findComponentTree(c.key, base)?.measures,
+ ...pick(c, ['analysisDate', 'key', 'name', 'qualifier']),
+ };
+ });
+
+ return this.reply({
+ baseComponent: base.component,
+ components: componentsMeasures.slice(ps * (p - 1), ps * (p - 1) + ps),
+ metrics: [],
+ paging: {
+ pageSize: ps,
+ pageIndex: p,
+ total: componentsMeasures.length,
+ },
+ });
+ };
+
+ handleGetTree = ({
+ component: key,
+ q = '',
+ qualifiers,
+ }: GetTreeParams & { qualifiers?: string }): Promise<{
+ baseComponent: TreeComponent;
+ components: TreeComponent[];
+ paging: Paging;
+ }> => {
+ const base = this.findComponentTree(key);
+ if (base === undefined) {
+ return Promise.reject({
+ errors: [{ msg: `No component has been found for key ${key}` }],
+ });
+ }
+ const components: TreeComponent[] = listAllComponent(base)
+ .filter(({ name, key }) => name.includes(q) || key.includes(q))
+ .filter(({ qualifier }) => (qualifiers?.length ? qualifiers.includes(qualifier) : true))
+ .map((c) => ({ ...c, visibility: Visibility.Public }));
+
+ return this.reply({
+ baseComponent: { ...base.component, visibility: Visibility.Public },
+ components,
+ paging: {
+ pageIndex: 1,
+ pageSize: 100,
+ total: components.length,
+ },
+ });
+ };
+
+ handleGetComponentData = (data: { component: string } & BranchParameters) => {
+ if (this.failLoadingComponentStatus !== undefined) {
+ return Promise.reject({ status: this.failLoadingComponentStatus });
+ }
+ const tree = this.findComponentTree(data.component);
+ if (tree) {
+ const { component, ancestors } = tree;
+ return this.reply({ component, ancestors });
+ }
+ throw new Error(`Couldn't find component with key ${data.component}`);
+ };
+
+ handleGetComponentForSourceViewer = ({ component }: { component: string } & BranchParameters) => {
+ const sourceFile = this.findSourceFile(component);
+ return this.reply(sourceFile.component);
+ };
+
+ handleGetDuplications = ({
+ key,
+ }: { key: string } & BranchParameters): Promise<{
+ duplications: Duplication[];
+ files: Dict<DuplicatedFile>;
+ }> => {
+ const { duplication } = this.findSourceFile(key);
+ if (duplication) {
+ return this.reply(duplication);
+ }
+ return this.reply({ duplications: [], files: {} });
+ };
+
+ handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => {
+ const { lines } = this.findSourceFile(data.key);
+ const from = data.from || 1;
+ const to = data.to || lines.length;
+ return this.reply(lines.slice(from - 1, to));
+ };
+
+ reply<T>(response: T): Promise<T> {
+ return Promise.resolve(cloneDeep(response));
+ }
+}
diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
index 177f06606da..b61ba990b00 100644
--- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
@@ -17,13 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { cloneDeep, keyBy, range, times } from 'lodash';
+import { cloneDeep, keyBy, times } from 'lodash';
import { RuleDescriptionSections } from '../../apps/coding-rules/rule';
-import {
- mockSnippetsByComponent,
- mockSourceLine,
- mockSourceViewerFile,
-} from '../../helpers/mocks/sources';
+import { mockSnippetsByComponent } from '../../helpers/mocks/sources';
import { RequestData } from '../../helpers/request';
import { getStandards } from '../../helpers/security-standard';
import {
@@ -33,7 +29,6 @@ import {
mockRawIssue,
mockRuleDetails,
} from '../../helpers/testMocks';
-import { BranchParameters } from '../../types/branch-like';
import {
IssueType,
RawFacet,
@@ -48,10 +43,8 @@ import {
RuleActivation,
RuleDetails,
SnippetsByComponent,
- SourceViewerFile,
} from '../../types/types';
import { NoticeType } from '../../types/users';
-import { getComponentForSourceViewer, getSources } from '../components';
import {
addIssueComment,
bulkChangeIssues,
@@ -100,20 +93,15 @@ interface IssueData {
export default class IssuesServiceMock {
isAdmin = false;
standards?: Standards;
- sourceViewerFiles: SourceViewerFile[];
+ defaultList: IssueData[];
list: IssueData[];
constructor() {
- // Comment should have their own store as we can test better CRUD operation
- this.sourceViewerFiles = [
- mockSourceViewerFile('file.foo', 'project'),
- mockSourceViewerFile('file.bar', 'project'),
- ];
- this.list = [
+ this.defaultList = [
{
issue: mockRawIssue(false, {
key: 'issue11',
- component: 'project:file.foo',
+ component: 'foo:test1.js',
message: 'FlowIssue',
rule: 'simpleRuleId',
textRange: {
@@ -128,7 +116,7 @@ export default class IssuesServiceMock {
description: 'Backtracking 1',
locations: [
{
- component: 'project:file.foo',
+ component: 'foo:test1.js',
msg: 'Data location 1',
textRange: {
startLine: 20,
@@ -138,7 +126,7 @@ export default class IssuesServiceMock {
},
},
{
- component: 'project:file.foo',
+ component: 'foo:test1.js',
msg: 'Data location 2',
textRange: {
startLine: 21,
@@ -153,7 +141,7 @@ export default class IssuesServiceMock {
type: FlowType.EXECUTION,
locations: [
{
- component: 'project:file.bar',
+ component: 'foo:test2.js',
msg: 'Execution location 1',
textRange: {
startLine: 20,
@@ -163,7 +151,7 @@ export default class IssuesServiceMock {
},
},
{
- component: 'project:file.bar',
+ component: 'foo:test2.js',
msg: 'Execution location 2',
textRange: {
startLine: 22,
@@ -173,7 +161,7 @@ export default class IssuesServiceMock {
},
},
{
- component: 'project:file.bar',
+ component: 'foo:test2.js',
msg: 'Execution location 3',
textRange: {
startLine: 5,
@@ -189,13 +177,13 @@ export default class IssuesServiceMock {
snippets: keyBy(
[
mockSnippetsByComponent(
- 'file.foo',
- 'project',
+ 'test1.js',
+ 'foo',
times(40, (i) => i + 1)
),
mockSnippetsByComponent(
- 'file.bar',
- 'project',
+ 'test2.js',
+ 'foo',
times(40, (i) => i + 1)
),
],
@@ -205,7 +193,7 @@ export default class IssuesServiceMock {
{
issue: mockRawIssue(false, {
key: 'issue0',
- component: 'project:file.foo',
+ component: 'foo:test1.js',
message: 'Issue on file',
rule: 'simpleRuleId',
textRange: undefined,
@@ -216,7 +204,7 @@ export default class IssuesServiceMock {
{
issue: mockRawIssue(false, {
key: 'issue1',
- component: 'project:file.foo',
+ component: 'foo:test1.js',
message: 'Fix this',
rule: 'simpleRuleId',
textRange: {
@@ -229,7 +217,7 @@ export default class IssuesServiceMock {
{
locations: [
{
- component: 'project:file.foo',
+ component: 'foo:test1.js',
msg: 'location 1',
textRange: {
startLine: 1,
@@ -243,7 +231,7 @@ export default class IssuesServiceMock {
{
locations: [
{
- component: 'project:file.bar',
+ component: 'foo:test2.js',
msg: 'location 2',
textRange: {
startLine: 20,
@@ -259,13 +247,13 @@ export default class IssuesServiceMock {
snippets: keyBy(
[
mockSnippetsByComponent(
- 'file.foo',
- 'project',
+ 'test1.js',
+ 'foo',
times(40, (i) => i + 1)
),
mockSnippetsByComponent(
- 'file.bar',
- 'project',
+ 'test2.js',
+ 'foo',
times(40, (i) => i + 1)
),
],
@@ -277,7 +265,7 @@ export default class IssuesServiceMock {
actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'],
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
key: 'issue2',
- component: 'project:file.bar',
+ component: 'foo:test2.js',
message: 'Fix that',
rule: 'advancedRuleId',
textRange: {
@@ -291,8 +279,8 @@ export default class IssuesServiceMock {
snippets: keyBy(
[
mockSnippetsByComponent(
- 'file.bar',
- 'project',
+ 'test2.js',
+ 'foo',
times(40, (i) => i + 20)
),
],
@@ -302,7 +290,7 @@ export default class IssuesServiceMock {
{
issue: mockRawIssue(false, {
key: 'issue3',
- component: 'project:file.bar',
+ component: 'foo:test2.js',
message: 'Second issue',
rule: 'other',
textRange: {
@@ -315,8 +303,8 @@ export default class IssuesServiceMock {
snippets: keyBy(
[
mockSnippetsByComponent(
- 'file.bar',
- 'project',
+ 'test2.js',
+ 'foo',
times(40, (i) => i + 20)
),
],
@@ -328,7 +316,7 @@ export default class IssuesServiceMock {
actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'],
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
key: 'issue4',
- component: 'project:file.bar',
+ component: 'foo:test2.js',
message: 'Issue with tags',
rule: 'external_eslint_repo:no-div-regex',
textRange: {
@@ -344,8 +332,8 @@ export default class IssuesServiceMock {
snippets: keyBy(
[
mockSnippetsByComponent(
- 'file.bar',
- 'project',
+ 'test2.js',
+ 'foo',
times(40, (i) => i + 20)
),
],
@@ -353,13 +341,12 @@ export default class IssuesServiceMock {
),
},
];
+
+ this.list = cloneDeep(this.defaultList);
+
(searchIssues as jest.Mock).mockImplementation(this.handleSearchIssues);
(getRuleDetails as jest.Mock).mockImplementation(this.handleGetRuleDetails);
(getIssueFlowSnippets as jest.Mock).mockImplementation(this.handleGetIssueFlowSnippets);
- (getSources as jest.Mock).mockImplementation(this.handleGetSources);
- (getComponentForSourceViewer as jest.Mock).mockImplementation(
- this.handleGetComponentForSourceViewer
- );
(bulkChangeIssues as jest.Mock).mockImplementation(this.handleBulkChangeIssues);
(getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser);
(dismissNotice as jest.Mock).mockImplementation(this.handleDismissNotification);
@@ -375,6 +362,10 @@ export default class IssuesServiceMock {
(searchIssueTags as jest.Mock).mockImplementation(this.handleSearchIssueTags);
}
+ reset = () => {
+ this.list = cloneDeep(this.defaultList);
+ };
+
async getStandards(): Promise<Standards> {
if (this.standards) {
return this.standards;
@@ -404,21 +395,6 @@ export default class IssuesServiceMock {
return this.reply({});
};
- handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => {
- return this.reply(range(data.from || 1, data.to || 10).map((line) => mockSourceLine({ line })));
- };
-
- handleGetComponentForSourceViewer = (data: { component: string } & BranchParameters) => {
- const file = this.sourceViewerFiles.find((f) => f.key === data.component);
- if (file === undefined) {
- return Promise.reject({
- errors: [{ msg: `No source file has been found for id ${data.component}` }],
- });
- }
-
- return this.reply(file);
- };
-
handleGetIssueFlowSnippets = (issueKey: string): Promise<Dict<SnippetsByComponent>> => {
const issue = this.list.find((i) => i.issue.key === issueKey);
if (issue === undefined) {
@@ -622,10 +598,14 @@ export default class IssuesServiceMock {
};
getActionsResponse = (overrides: Partial<RawIssue>, issueKey: string) => {
- const issueDataSelected = this.list.find((l) => l.issue.key === issueKey)!;
+ const issueDataSelected = this.list.find((l) => l.issue.key === issueKey);
+
+ if (!issueDataSelected) {
+ throw new Error(`Coulnd't find issue for key ${issueKey}`);
+ }
issueDataSelected.issue = {
- ...issueDataSelected?.issue,
+ ...issueDataSelected.issue,
...overrides,
};
diff --git a/server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts
deleted file mode 100644
index 90ff8c7098d..00000000000
--- a/server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-/* eslint-disable no-template-curly-in-string */
-
-import { cloneDeep, pick, times } from 'lodash';
-import {
- getComponentData,
- getComponentForSourceViewer,
- getDuplications,
- getSources,
-} from '../../api/components';
-import { mockSourceLine } from '../../helpers/mocks/sources';
-import { HttpStatus } from '../../helpers/request';
-import { BranchParameters } from '../../types/branch-like';
-import { Dict } from '../../types/types';
-import { setIssueSeverity } from '../issues';
-
-function mockSourceFileView(name: string, project = 'project') {
- return {
- component: {
- key: `${project}:${name}`,
- name,
- qualifier: 'FIL',
- path: name,
- language: 'js',
- analysisDate: '2019-08-08T12:15:12+0200',
- leakPeriodDate: '2018-08-07T11:22:22+0200',
- version: '1.2-SNAPSHOT',
- needIssueSync: false,
- },
- sourceFileView: {
- key: `${project}:${name}`,
- uuid: `AWMgNpveti8CNlpVyHAm${project}${name}`,
- path: name,
- name,
- longName: name,
- q: 'FIL',
- project,
- projectName: 'Test project',
- fav: false,
- canMarkAsFavorite: true,
- measures: { lines: '0', issues: '0' },
- },
- };
-}
-
-const ANCESTORS = [
- {
- key: 'project',
- name: 'Test project',
- description: '',
- qualifier: 'TRK',
- analysisDate: '2019-08-08T12:15:12+0200',
- tags: [],
- visibility: 'public',
- leakPeriodDate: '2018-08-07T11:22:22+0200',
- version: '1.2-SNAPSHOT',
- needIssueSync: false,
- },
-];
-const FILES: Dict<any> = {
- 'project:test3.js': {
- ...mockSourceFileView('test3.js'),
- sources: [],
- ancestors: ANCESTORS,
- },
- 'project:test2.js': {
- ...mockSourceFileView('test2.js'),
- sources: times(200, (n) =>
- mockSourceLine({
- line: n,
- code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`,
- })
- ),
- ancestors: ANCESTORS,
- },
- 'foo:index.tsx': {
- ...mockSourceFileView('index.tsx', 'foo'),
- sources: times(200, (n) =>
- mockSourceLine({
- line: n,
- code: 'function Test() {}',
- })
- ),
- ancestors: ANCESTORS,
- },
- 'project:testSymb.tsx': {
- ...mockSourceFileView('testSymb.tsx'),
- sources: times(20, (n) =>
- mockSourceLine({
- line: n,
- code: ' <span class="sym-35 sym">symbole</span>',
- })
- ),
- ancestors: ANCESTORS,
- },
- 'project:test.js': {
- ...mockSourceFileView('test.js'),
- sources: [
- {
- line: 1,
- code: '\u003cspan class\u003d"cd"\u003e/*\u003c/span\u003e',
- scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
- scmAuthor: 'stas.vilchik@sonarsource.com',
- scmDate: '2018-07-10T20:21:20+0200',
- duplicated: false,
- isNew: false,
- lineHits: 1,
- coveredConditions: 1,
- },
- {
- line: 2,
- code: '\u003cspan class\u003d"cd"\u003e * SonarQube\u003c/span\u003e',
- scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
- scmAuthor: 'stas.vilchik@sonarsource.com',
- scmDate: '2018-07-10T20:21:20+0200',
- duplicated: false,
- isNew: false,
- lineHits: 0,
- conditions: 1,
- },
- {
- line: 3,
- code: '\u003cspan class\u003d"cd"\u003e * Copyright\u003c/span\u003e',
- scmRevision: '89a3d21bc28f2fa6201b5e8b1185d5358481b3dd',
- scmAuthor: 'pierre.guillot@sonarsource.com',
- scmDate: '2022-01-28T21:03:07+0100',
- duplicated: false,
- isNew: false,
- lineHits: 1,
- },
- {
- line: 4,
- code: '\u003cspan class\u003d"cd"\u003e * mailto:info AT sonarsource DOT com\u003c/span\u003e',
- scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
- scmAuthor: 'stas.vilchik@sonarsource.com',
- duplicated: false,
- isNew: false,
- lineHits: 1,
- conditions: 1,
- coveredConditions: 1,
- },
- {
- line: 5,
- code: '\u003cspan class\u003d"cd"\u003e * 5\u003c/span\u003e',
- scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
- duplicated: false,
- isNew: false,
- lineHits: 2,
- conditions: 2,
- coveredConditions: 1,
- },
- {
- line: 6,
- code: '\u003cspan class\u003d"cd"\u003e * 6\u003c/span\u003e',
- scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
- duplicated: false,
- isNew: false,
- lineHits: 0,
- },
- {
- line: 7,
- code: '\u003cspan class\u003d"cd"\u003e * 7\u003c/span\u003e',
- scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
- duplicated: true,
- isNew: true,
- },
- {
- code: '\u003cspan class\u003d"cd"\u003e * This program is free software; you can redistribute it and/or\u003c/span\u003e',
- scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
- scmAuthor: 'stas.vilchik@sonarsource.com',
- scmDate: '2018-07-10T20:21:20+0200',
- duplicated: false,
- isNew: false,
- },
- ],
- ancestors: ANCESTORS,
- duplications: [
- {
- blocks: [
- { from: 7, size: 1, _ref: '1' },
- { from: 1, size: 1, _ref: '2' },
- ],
- },
- ],
- files: {
- '1': {
- key: 'project:test.js',
- name: 'test.js',
- uuid: 'AX8NSmj8EGYw5-dyy63J',
- project: 'project',
- projectUuid: 'AX7juKJqVeQLJMPyb_b-',
- projectName: 'project',
- },
- '2': {
- key: 'project:test2.js',
- name: 'test2.js',
- uuid: 'BX8NSmj8EGYw5-dyy63J',
- project: 'project',
- projectUuid: 'AX7juKJqVeQLJMPyb_b-',
- projectName: 'project',
- },
- },
- },
-};
-
-export class SourceViewerServiceMock {
- faileLoadingComponentStatus: HttpStatus | undefined = undefined;
-
- constructor() {
- (getComponentData as jest.Mock).mockImplementation(this.handleGetComponentData);
- (getComponentForSourceViewer as jest.Mock).mockImplementation(
- this.handleGetComponentForSourceViewer
- );
- (getDuplications as jest.Mock).mockImplementation(this.handleGetDuplications);
- (getSources as jest.Mock).mockImplementation(this.handleGetSources);
- (setIssueSeverity as jest.Mock).mockImplementation(this.handleSetIssueSeverity);
- }
-
- reset() {
- this.faileLoadingComponentStatus = undefined;
- }
-
- setFailLoadingComponentStatus(status: HttpStatus.Forbidden | HttpStatus.NotFound) {
- this.faileLoadingComponentStatus = status;
- }
-
- getHugeFile(): string {
- return 'project:test2.js';
- }
-
- getEmptyFile(): string {
- return 'project:test3.js';
- }
-
- getFileWithSource(): string {
- return 'project:test.js';
- }
-
- handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => {
- const { sources } = FILES[data.key];
- const from = data.from || 1;
- const to = data.to || sources.length;
- return this.reply(sources.slice(from - 1, to));
- };
-
- handleGetDuplications = (data: { key: string } & BranchParameters) => {
- return this.reply(pick(FILES[data.key], ['duplications', 'files']));
- };
-
- handleGetComponentForSourceViewer = (data: { component: string } & BranchParameters) => {
- return this.reply(FILES[data.component]['sourceFileView']);
- };
-
- handleGetComponentData = (data: { component: string } & BranchParameters) => {
- if (this.faileLoadingComponentStatus !== undefined) {
- return Promise.reject({ status: this.faileLoadingComponentStatus });
- }
- return this.reply(pick(FILES[data.component], ['component', 'ancestor']));
- };
-
- handleSetIssueSeverity = () => {
- return this.reply({});
- };
-
- reply<T>(response: T): Promise<T> {
- return Promise.resolve(cloneDeep(response));
- }
-}