From 107c0479a8a6bf6dcd7e30da9bb6d63e31750f49 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Fri, 26 May 2023 15:11:32 +0200 Subject: [PATCH] SONAR-18425 Lay groundwork to start sharing data between service mocks --- .../js/api/mocks/ComponentsServiceMock.ts | 323 +------------- .../main/js/api/mocks/IssuesServiceMock.ts | 390 +---------------- .../src/main/js/api/mocks/data/components.ts | 413 ++++++++++++++++++ .../src/main/js/api/mocks/data/ids.ts | 72 +++ .../src/main/js/api/mocks/data/issues.ts | 382 ++++++++++++++++ .../src/main/js/api/mocks/data/rules.ts | 54 +++ 6 files changed, 938 insertions(+), 696 deletions(-) create mode 100644 server/sonar-web/src/main/js/api/mocks/data/components.ts create mode 100644 server/sonar-web/src/main/js/api/mocks/data/ids.ts create mode 100644 server/sonar-web/src/main/js/api/mocks/data/issues.ts create mode 100644 server/sonar-web/src/main/js/api/mocks/data/rules.ts diff --git a/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts index df199501842..15573b1dbcf 100644 --- a/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts @@ -17,29 +17,18 @@ * 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 { cloneDeep, flatMap, map, pick } from 'lodash'; import { HttpStatus, RequestData } from '../../helpers/request'; import { BranchParameters } from '../../types/branch-like'; -import { ComponentQualifier, TreeComponent, Visibility } from '../../types/component'; +import { TreeComponent, Visibility } from '../../types/component'; import { Component, ComponentMeasure, Dict, DuplicatedFile, Duplication, - Measure, Metric, Paging, - SourceLine, - SourceViewerFile, } from '../../types/types'; import { GetTreeParams, @@ -52,22 +41,12 @@ import { getSources, getTree, } from '../components'; - -interface ComponentTree { - component: Component; - ancestors: Component[]; - measures?: Measure[]; - children: ComponentTree[]; -} - -interface SourceFile { - component: SourceViewerFile; - lines: SourceLine[]; - duplication?: { - duplications: Duplication[]; - files: Dict; - }; -} +import { + ComponentTree, + SourceFile, + mockFullComponentTree, + mockFullSourceViewerFileList, +} from './data/components'; function isLeaf(node: ComponentTree) { return node.children.length === 0; @@ -100,290 +79,8 @@ export default class ComponentsServiceMock { 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: ' symbole', - }) - ), - }, - ] as SourceFile[]); + this.defaultComponents = components || [mockFullComponentTree()]; + this.defaultSourceFiles = sourceFiles || mockFullSourceViewerFileList(); this.components = cloneDeep(this.defaultComponents); this.sourceFiles = cloneDeep(this.defaultSourceFiles); 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 d79208bedcd..a39e74e6c6a 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -17,26 +17,16 @@ * 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, times, uniqueId } from 'lodash'; +import { cloneDeep, uniqueId } from 'lodash'; import { RuleDescriptionSections } from '../../apps/coding-rules/rule'; import { mockIssueChangelog } from '../../helpers/mocks/issues'; -import { mockSnippetsByComponent } from '../../helpers/mocks/sources'; import { RequestData } from '../../helpers/request'; import { getStandards } from '../../helpers/security-standard'; -import { - mockLoggedInUser, - mockPaging, - mockRawIssue, - mockRule, - mockRuleDetails, -} from '../../helpers/testMocks'; +import { mockLoggedInUser, mockPaging, mockRuleDetails } from '../../helpers/testMocks'; import { SearchRulesResponse } from '../../types/coding-rules'; import { ASSIGNEE_ME, - IssueActions, IssueResolution, - IssueScope, - IssueSeverity, IssueStatus, IssueTransition, IssueType, @@ -47,14 +37,7 @@ import { } from '../../types/issues'; import { SearchRulesQuery } from '../../types/rules'; import { Standards } from '../../types/security'; -import { - Dict, - FlowType, - Rule, - RuleActivation, - RuleDetails, - SnippetsByComponent, -} from '../../types/types'; +import { Dict, Rule, RuleActivation, RuleDetails, SnippetsByComponent } from '../../types/types'; import { LoggedInUser, NoticeType } from '../../types/users'; import { addIssueComment, @@ -73,6 +56,8 @@ import { } from '../issues'; import { getRuleDetails, searchRules } from '../rules'; import { dismissNotice, getCurrentUser, searchUsers } from '../users'; +import { mockIssuesList } from './data/issues'; +import { mockRuleList } from './data/rules'; function mockReferenceComponent(override?: Partial) { return { @@ -112,369 +97,8 @@ export default class IssuesServiceMock { constructor() { this.currentUser = mockLoggedInUser(); - this.defaultList = [ - { - issue: mockRawIssue(false, { - key: 'issue101', - component: 'foo:test1.js', - creationDate: '2023-01-05T09:36:01+0100', - message: 'Issue with no location message', - type: IssueType.Vulnerability, - rule: 'simpleRuleId', - textRange: { - startLine: 10, - endLine: 10, - startOffset: 0, - endOffset: 2, - }, - flows: [ - { - locations: [ - { - component: 'foo:test1.js', - textRange: { - startLine: 1, - endLine: 1, - startOffset: 0, - endOffset: 1, - }, - }, - ], - }, - { - locations: [ - { - component: 'foo:test2.js', - textRange: { - startLine: 20, - endLine: 20, - startOffset: 0, - endOffset: 1, - }, - }, - ], - }, - ], - resolution: IssueResolution.WontFix, - scope: IssueScope.Main, - tags: ['tag0', 'tag1'], - }), - snippets: keyBy( - [ - mockSnippetsByComponent( - 'test1.js', - 'foo', - times(40, (i) => i + 1) - ), - mockSnippetsByComponent( - 'test2.js', - 'foo', - times(40, (i) => i + 1) - ), - ], - 'component.key' - ), - }, - { - issue: mockRawIssue(false, { - key: 'issue11', - component: 'foo:test1.js', - creationDate: '2022-01-01T09:36:01+0100', - message: 'FlowIssue', - type: IssueType.CodeSmell, - severity: IssueSeverity.Minor, - rule: 'simpleRuleId', - textRange: { - startLine: 10, - endLine: 10, - startOffset: 0, - endOffset: 2, - }, - flows: [ - { - type: FlowType.DATA, - description: 'Backtracking 1', - locations: [ - { - component: 'foo:test1.js', - msg: 'Data location 1', - textRange: { - startLine: 20, - endLine: 20, - startOffset: 0, - endOffset: 1, - }, - }, - { - component: 'foo:test1.js', - msg: 'Data location 2', - textRange: { - startLine: 21, - endLine: 21, - startOffset: 0, - endOffset: 1, - }, - }, - ], - }, - { - type: FlowType.EXECUTION, - locations: [ - { - component: 'foo:test2.js', - msg: 'Execution location 1', - textRange: { - startLine: 20, - endLine: 20, - startOffset: 0, - endOffset: 1, - }, - }, - { - component: 'foo:test2.js', - msg: 'Execution location 2', - textRange: { - startLine: 22, - endLine: 22, - startOffset: 0, - endOffset: 1, - }, - }, - { - component: 'foo:test2.js', - msg: 'Execution location 3', - textRange: { - startLine: 5, - endLine: 5, - startOffset: 0, - endOffset: 1, - }, - }, - ], - }, - ], - tags: ['tag1'], - }), - snippets: keyBy( - [ - mockSnippetsByComponent( - 'test1.js', - 'foo', - times(40, (i) => i + 1) - ), - mockSnippetsByComponent( - 'test2.js', - 'foo', - times(40, (i) => i + 1) - ), - ], - 'component.key' - ), - }, - { - issue: mockRawIssue(false, { - key: 'issue0', - component: 'foo:test1.js', - message: 'Issue on file', - assignee: mockLoggedInUser().login, - type: IssueType.CodeSmell, - rule: 'simpleRuleId', - textRange: undefined, - line: undefined, - scope: IssueScope.Test, - }), - snippets: {}, - }, - { - issue: mockRawIssue(false, { - key: 'issue1', - component: 'foo:huge.js', - message: 'Fix this', - type: IssueType.Vulnerability, - rule: 'simpleRuleId', - textRange: { - startLine: 10, - endLine: 10, - startOffset: 0, - endOffset: 2, - }, - flows: [ - { - locations: [ - { - component: 'foo:huge.js', - msg: 'location 1', - textRange: { - startLine: 1, - endLine: 1, - startOffset: 0, - endOffset: 1, - }, - }, - ], - }, - { - locations: [ - { - component: 'foo:huge.js', - msg: 'location 2', - textRange: { - startLine: 50, - endLine: 50, - startOffset: 0, - endOffset: 1, - }, - }, - ], - }, - ], - }), - snippets: keyBy( - [ - mockSnippetsByComponent( - 'huge.js', - 'foo', - times(80, (i) => i + 1) - ), - mockSnippetsByComponent( - 'huge.js', - 'foo', - times(80, (i) => i + 1) - ), - ], - 'component.key' - ), - }, - { - issue: mockRawIssue(false, { - actions: Object.values(IssueActions), - transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], - key: 'issue2', - component: 'foo:test2.js', - message: 'Fix that', - rule: 'advancedRuleId', - textRange: { - startLine: 25, - endLine: 25, - startOffset: 0, - endOffset: 1, - }, - ruleDescriptionContextKey: 'spring', - resolution: IssueResolution.Unresolved, - status: IssueStatus.Open, - }), - snippets: keyBy( - [ - mockSnippetsByComponent( - 'test2.js', - 'foo', - times(40, (i) => i + 20) - ), - ], - 'component.key' - ), - }, - { - issue: mockRawIssue(false, { - key: 'issue3', - component: 'foo:test2.js', - message: 'Second issue', - rule: 'other', - textRange: { - startLine: 28, - endLine: 28, - startOffset: 0, - endOffset: 1, - }, - resolution: IssueResolution.Fixed, - status: IssueStatus.Confirmed, - }), - snippets: keyBy( - [ - mockSnippetsByComponent( - 'test2.js', - 'foo', - times(40, (i) => i + 20) - ), - ], - 'component.key' - ), - }, - { - issue: mockRawIssue(false, { - actions: Object.values(IssueActions), - transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], - key: 'issue4', - component: 'foo:test2.js', - message: 'Issue with tags', - rule: 'other', - textRange: { - startLine: 25, - endLine: 25, - startOffset: 0, - endOffset: 1, - }, - ruleDescriptionContextKey: 'spring', - ruleStatus: 'DEPRECATED', - quickFixAvailable: true, - tags: ['unused'], - codeVariants: ['variant 1', 'variant 2'], - project: 'org.project2', - assignee: 'email1@sonarsource.com', - author: 'email3@sonarsource.com', - }), - snippets: keyBy( - [ - mockSnippetsByComponent( - 'test2.js', - 'foo', - times(40, (i) => i + 20) - ), - ], - 'component.key' - ), - }, - { - issue: mockRawIssue(false, { - key: 'issue1101', - component: 'foo:test5.js', - message: 'Issue on page 2', - rule: 'simpleRuleId', - textRange: undefined, - line: undefined, - }), - snippets: {}, - }, - ]; - this.rulesList = [ - mockRule({ - key: 'simpleRuleId', - name: 'Simple rule', - lang: 'java', - langName: 'Java', - type: 'CODE_SMELL', - }), - mockRule({ - key: 'advancedRuleId', - name: 'Advanced rule', - lang: 'web', - langName: 'HTML', - type: 'VULNERABILITY', - }), - mockRule({ - key: 'cpp:S6069', - lang: 'cpp', - langName: 'C++', - name: 'Security hotspot rule', - type: 'SECURITY_HOTSPOT', - }), - mockRule({ - key: 'tsql:S131', - name: '"CASE" expressions should end with "ELSE" clauses', - lang: 'tsql', - langName: 'T-SQL', - }), - ]; + this.defaultList = mockIssuesList(); + this.rulesList = mockRuleList(); this.list = cloneDeep(this.defaultList); diff --git a/server/sonar-web/src/main/js/api/mocks/data/components.ts b/server/sonar-web/src/main/js/api/mocks/data/components.ts new file mode 100644 index 00000000000..75497170265 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/data/components.ts @@ -0,0 +1,413 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { times } from 'lodash'; +import { isDiffMetric } from '../../../helpers/measures'; +import { mockComponent } from '../../../helpers/mocks/component'; +import { + mockDuplicatedFile, + mockDuplication, + mockDuplicationBlock, + mockSourceLine, + mockSourceViewerFile, +} from '../../../helpers/mocks/sources'; +import { mockMeasure } from '../../../helpers/testMocks'; +import { ComponentQualifier } from '../../../types/component'; +import { MetricKey } from '../../../types/metrics'; +import { + Component, + Dict, + DuplicatedFile, + Duplication, + Measure, + SourceLine, + SourceViewerFile, +} from '../../../types/types'; +import { + FILE1_KEY, + FILE2_KEY, + FILE3_KEY, + FILE4_KEY, + FILE5_KEY, + FILE6_KEY, + FILE7_KEY, + FOLDER1_KEY, + PARENT_COMPONENT_KEY, +} from './ids'; + +export interface ComponentTree { + component: Component; + ancestors: Component[]; + measures?: Measure[]; + children: ComponentTree[]; +} + +export interface SourceFile { + component: SourceViewerFile; + lines: SourceLine[]; + duplication?: { + duplications: Duplication[]; + files: Dict; + }; +} + +export function mockFullComponentTree( + baseComponent = mockComponent({ + key: PARENT_COMPONENT_KEY, + name: 'Foo', + breadcrumbs: [ + { key: PARENT_COMPONENT_KEY, name: 'Foo', qualifier: ComponentQualifier.Project }, + ], + }) +): ComponentTree { + const folderComponent = mockComponent({ + key: `${baseComponent.key}:${FOLDER1_KEY}`, + name: FOLDER1_KEY, + path: FOLDER1_KEY, + qualifier: ComponentQualifier.Directory, + breadcrumbs: [ + ...baseComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FOLDER1_KEY}`, + name: FOLDER1_KEY, + qualifier: ComponentQualifier.Directory, + }, + ], + }); + const measures = [ + ...Object.values(MetricKey) + .filter((metric) => metric !== MetricKey.alert_status) + .map((metric) => + isDiffMetric(metric) + ? mockMeasure({ metric, period: { index: 1, value: '1.0' } }) + : mockMeasure({ metric, value: '2.0', period: undefined }) + ), + mockMeasure({ + metric: MetricKey.alert_status, + value: 'OK', + period: undefined, + }), + ]; + + return { + component: baseComponent, + ancestors: [], + measures, + children: [ + { + component: folderComponent, + ancestors: [baseComponent], + + measures, + children: [ + { + component: mockComponent({ + key: `${baseComponent.key}:${FOLDER1_KEY}/${FILE7_KEY}`, + name: FILE7_KEY, + path: `${FOLDER1_KEY}/${FILE7_KEY}`, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...folderComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FOLDER1_KEY}/${FILE7_KEY}`, + name: FILE7_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent, folderComponent], + + measures, + children: [], + }, + ], + }, + { + component: mockComponent({ + key: `${baseComponent.key}:${FILE1_KEY}`, + name: FILE1_KEY, + path: FILE1_KEY, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...baseComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FILE1_KEY}`, + name: FILE1_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent], + + measures, + children: [], + }, + { + component: mockComponent({ + key: `${baseComponent.key}:${FILE2_KEY}`, + name: FILE2_KEY, + path: FILE2_KEY, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...baseComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FILE2_KEY}`, + name: FILE2_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent], + + measures, + children: [], + }, + { + component: mockComponent({ + key: `${baseComponent.key}:${FILE3_KEY}`, + name: FILE3_KEY, + path: FILE3_KEY, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...baseComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FILE3_KEY}`, + name: FILE3_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent], + + measures, + children: [], + }, + { + component: mockComponent({ + key: `${baseComponent.key}:${FILE4_KEY}`, + name: FILE4_KEY, + path: FILE4_KEY, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...baseComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FILE4_KEY}`, + name: FILE4_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent], + + measures, + children: [], + }, + { + component: mockComponent({ + key: `${baseComponent.key}:${FILE5_KEY}`, + name: FILE5_KEY, + path: FILE5_KEY, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...baseComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FILE5_KEY}`, + name: FILE5_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent], + + measures, + children: [], + }, + { + component: mockComponent({ + key: `${baseComponent.key}:${FILE6_KEY}`, + name: FILE6_KEY, + path: FILE6_KEY, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...baseComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FILE6_KEY}`, + name: FILE6_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent], + + measures, + children: [], + }, + ], + }; +} + +export function mockFullSourceViewerFileList(baseComponentKey = PARENT_COMPONENT_KEY) { + return [ + { + component: mockSourceViewerFile(FILE1_KEY, baseComponentKey), + lines: times(50, (n) => + mockSourceLine({ + line: n, + code: 'function Test() {}', + }) + ), + }, + { + component: mockSourceViewerFile(`${FOLDER1_KEY}/${FILE7_KEY}`, baseComponentKey), + lines: times(50, (n) => + mockSourceLine({ + line: n, + code: 'function Test() {}', + }) + ), + }, + { + component: mockSourceViewerFile(FILE2_KEY, baseComponentKey), + 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: `${baseComponentKey}:${FILE2_KEY}` }), + '2': mockDuplicatedFile({ key: `${baseComponentKey}:${FILE3_KEY}` }), + }, + }, + }, + { + component: mockSourceViewerFile(FILE3_KEY, baseComponentKey), + lines: times(50, (n) => + mockSourceLine({ + line: n, + code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`, + }) + ), + }, + { + component: mockSourceViewerFile(FILE5_KEY, baseComponentKey), + lines: [], + }, + { + component: mockSourceViewerFile(FILE6_KEY, baseComponentKey), + lines: times(200, (n) => + mockSourceLine({ + line: n, + code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`, + }) + ), + }, + { + component: mockSourceViewerFile(FILE4_KEY, baseComponentKey), + lines: times(20, (n) => + mockSourceLine({ + line: n, + code: ' symbole', + }) + ), + }, + ] as SourceFile[]; +} diff --git a/server/sonar-web/src/main/js/api/mocks/data/ids.ts b/server/sonar-web/src/main/js/api/mocks/data/ids.ts new file mode 100644 index 00000000000..38b83514e7e --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/data/ids.ts @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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. + */ + +// Component tree. +export const PARENT_COMPONENT_KEY = 'foo'; +//// Root folder. +export const FILE1_KEY = 'index.tsx'; +export const FILE2_KEY = 'test1.js'; +export const FILE3_KEY = 'test2.js'; +export const FILE4_KEY = 'testSymb.tsx'; +export const FILE5_KEY = 'empty.js'; +export const FILE6_KEY = 'huge.js'; +export const FOLDER1_KEY = 'folderA'; +//// Inside folderA. +export const FILE7_KEY = 'out.tsx'; + +// Rules. +export const SIMPLE_RULE = 'simpleRuleId'; +export const ADVANCED_RULE = 'advancedRuleId'; +export const S6069_RULE = 'cpp:S6069'; +export const S131_RULE = 'tsql:S131'; + +// Issues. +export const ISSUE_0 = 'issue0'; +export const ISSUE_1 = 'issue1'; +export const ISSUE_2 = 'issue2'; +export const ISSUE_3 = 'issue3'; +export const ISSUE_4 = 'issue4'; +export const ISSUE_11 = 'issue11'; +export const ISSUE_101 = 'issue101'; +export const ISSUE_1101 = 'issue1101'; + +// Issue to rule map. +export const ISSUE_TO_RULE = { + [ISSUE_0]: SIMPLE_RULE, + [ISSUE_1]: SIMPLE_RULE, + [ISSUE_2]: ADVANCED_RULE, + [ISSUE_3]: 'other', + [ISSUE_4]: 'other', + [ISSUE_11]: SIMPLE_RULE, + [ISSUE_101]: SIMPLE_RULE, + [ISSUE_1101]: SIMPLE_RULE, +}; + +// Issue to files map. +export const ISSUE_TO_FILES = { + [ISSUE_0]: [FILE2_KEY], + [ISSUE_1]: [FILE6_KEY], + [ISSUE_2]: [FILE3_KEY], + [ISSUE_3]: [FILE3_KEY], + [ISSUE_4]: [FILE3_KEY], + [ISSUE_11]: [FILE2_KEY, FILE3_KEY], + [ISSUE_101]: [FILE2_KEY, FILE3_KEY], + [ISSUE_1101]: [FILE7_KEY], +}; diff --git a/server/sonar-web/src/main/js/api/mocks/data/issues.ts b/server/sonar-web/src/main/js/api/mocks/data/issues.ts new file mode 100644 index 00000000000..86fe6aa2755 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/data/issues.ts @@ -0,0 +1,382 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { keyBy, times } from 'lodash'; +import { mockSnippetsByComponent } from '../../../helpers/mocks/sources'; +import { mockLoggedInUser, mockRawIssue } from '../../../helpers/testMocks'; +import { + IssueActions, + IssueResolution, + IssueScope, + IssueSeverity, + IssueStatus, + IssueType, +} from '../../../types/issues'; +import { FlowType } from '../../../types/types'; +import { + ISSUE_0, + ISSUE_1, + ISSUE_101, + ISSUE_11, + ISSUE_1101, + ISSUE_2, + ISSUE_3, + ISSUE_4, + ISSUE_TO_FILES, + ISSUE_TO_RULE, + PARENT_COMPONENT_KEY, +} from './ids'; + +export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY) { + return [ + { + issue: mockRawIssue(false, { + key: ISSUE_101, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_101][0]}`, + creationDate: '2023-01-05T09:36:01+0100', + message: 'Issue with no location message', + type: IssueType.Vulnerability, + rule: ISSUE_TO_RULE[ISSUE_101], + textRange: { + startLine: 10, + endLine: 10, + startOffset: 0, + endOffset: 2, + }, + flows: [ + { + locations: [ + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_101][0]}`, + textRange: { + startLine: 1, + endLine: 1, + startOffset: 0, + endOffset: 1, + }, + }, + ], + }, + { + locations: [ + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_101][1]}`, + textRange: { + startLine: 20, + endLine: 20, + startOffset: 0, + endOffset: 1, + }, + }, + ], + }, + ], + resolution: IssueResolution.WontFix, + scope: IssueScope.Main, + tags: ['tag0', 'tag1'], + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_101][0], + baseComponentKey, + times(40, (i) => i + 1) + ), + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_101][1], + baseComponentKey, + times(40, (i) => i + 1) + ), + ], + 'component.key' + ), + }, + { + issue: mockRawIssue(false, { + key: ISSUE_11, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][0]}`, + creationDate: '2022-01-01T09:36:01+0100', + message: 'FlowIssue', + type: IssueType.CodeSmell, + severity: IssueSeverity.Minor, + rule: ISSUE_TO_RULE[ISSUE_11], + textRange: { + startLine: 10, + endLine: 10, + startOffset: 0, + endOffset: 2, + }, + flows: [ + { + type: FlowType.DATA, + description: 'Backtracking 1', + locations: [ + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][0]}`, + msg: 'Data location 1', + textRange: { + startLine: 20, + endLine: 20, + startOffset: 0, + endOffset: 1, + }, + }, + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][0]}`, + msg: 'Data location 2', + textRange: { + startLine: 21, + endLine: 21, + startOffset: 0, + endOffset: 1, + }, + }, + ], + }, + { + type: FlowType.EXECUTION, + locations: [ + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][1]}`, + msg: 'Execution location 1', + textRange: { + startLine: 20, + endLine: 20, + startOffset: 0, + endOffset: 1, + }, + }, + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][1]}`, + msg: 'Execution location 2', + textRange: { + startLine: 22, + endLine: 22, + startOffset: 0, + endOffset: 1, + }, + }, + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][1]}`, + msg: 'Execution location 3', + textRange: { + startLine: 5, + endLine: 5, + startOffset: 0, + endOffset: 1, + }, + }, + ], + }, + ], + tags: ['tag1'], + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_11][0], + baseComponentKey, + times(40, (i) => i + 1) + ), + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_11][1], + baseComponentKey, + times(40, (i) => i + 1) + ), + ], + 'component.key' + ), + }, + { + issue: mockRawIssue(false, { + key: ISSUE_0, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_0][0]}`, + message: 'Issue on file', + assignee: mockLoggedInUser().login, + type: IssueType.CodeSmell, + rule: ISSUE_TO_RULE[ISSUE_0], + textRange: undefined, + line: undefined, + scope: IssueScope.Test, + }), + snippets: {}, + }, + { + issue: mockRawIssue(false, { + key: ISSUE_1, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1][0]}`, + message: 'Fix this', + type: IssueType.Vulnerability, + rule: ISSUE_TO_RULE[ISSUE_1], + textRange: { + startLine: 10, + endLine: 10, + startOffset: 0, + endOffset: 2, + }, + flows: [ + { + locations: [ + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1][0]}`, + msg: 'location 1', + textRange: { + startLine: 1, + endLine: 1, + startOffset: 0, + endOffset: 1, + }, + }, + ], + }, + { + locations: [ + { + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1][0]}`, + msg: 'location 2', + textRange: { + startLine: 50, + endLine: 50, + startOffset: 0, + endOffset: 1, + }, + }, + ], + }, + ], + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_1][0], + baseComponentKey, + times(80, (i) => i + 1) + ), + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_1][0], + baseComponentKey, + times(80, (i) => i + 1) + ), + ], + 'component.key' + ), + }, + { + issue: mockRawIssue(false, { + key: ISSUE_2, + actions: Object.values(IssueActions), + transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_2][0]}`, + message: 'Fix that', + rule: ISSUE_TO_RULE[ISSUE_2], + textRange: { + startLine: 25, + endLine: 25, + startOffset: 0, + endOffset: 1, + }, + ruleDescriptionContextKey: 'spring', + resolution: IssueResolution.Unresolved, + status: IssueStatus.Open, + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_2][0], + baseComponentKey, + times(40, (i) => i + 20) + ), + ], + 'component.key' + ), + }, + { + issue: mockRawIssue(false, { + key: ISSUE_3, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_3][0]}`, + message: 'Second issue', + rule: ISSUE_TO_RULE[ISSUE_3], + textRange: { + startLine: 28, + endLine: 28, + startOffset: 0, + endOffset: 1, + }, + resolution: IssueResolution.Fixed, + status: IssueStatus.Confirmed, + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_3][0], + baseComponentKey, + times(40, (i) => i + 20) + ), + ], + 'component.key' + ), + }, + { + issue: mockRawIssue(false, { + key: ISSUE_4, + actions: Object.values(IssueActions), + transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_4][0]}`, + message: 'Issue with tags', + rule: ISSUE_TO_RULE[ISSUE_4], + textRange: { + startLine: 25, + endLine: 25, + startOffset: 0, + endOffset: 1, + }, + ruleDescriptionContextKey: 'spring', + ruleStatus: 'DEPRECATED', + quickFixAvailable: true, + tags: ['unused'], + codeVariants: ['variant 1', 'variant 2'], + project: 'org.project2', + assignee: 'email1@sonarsource.com', + author: 'email3@sonarsource.com', + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + ISSUE_TO_FILES[ISSUE_4][0], + baseComponentKey, + times(40, (i) => i + 20) + ), + ], + 'component.key' + ), + }, + { + issue: mockRawIssue(false, { + key: ISSUE_1101, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1101][0]}`, + message: 'Issue on page 2', + rule: ISSUE_TO_RULE[ISSUE_1101], + textRange: undefined, + line: undefined, + }), + snippets: {}, + }, + ]; +} diff --git a/server/sonar-web/src/main/js/api/mocks/data/rules.ts b/server/sonar-web/src/main/js/api/mocks/data/rules.ts new file mode 100644 index 00000000000..de3de9bf095 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/data/rules.ts @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { mockRule } from '../../../helpers/testMocks'; +import { ADVANCED_RULE, S131_RULE, S6069_RULE, SIMPLE_RULE } from './ids'; + +export function mockRuleList() { + return [ + mockRule({ + key: SIMPLE_RULE, + name: 'Simple rule', + lang: 'java', + langName: 'Java', + type: 'CODE_SMELL', + }), + mockRule({ + key: ADVANCED_RULE, + name: 'Advanced rule', + lang: 'web', + langName: 'HTML', + type: 'VULNERABILITY', + }), + mockRule({ + key: S6069_RULE, + lang: 'cpp', + langName: 'C++', + name: 'Security hotspot rule', + type: 'SECURITY_HOTSPOT', + }), + mockRule({ + key: S131_RULE, + name: '"CASE" expressions should end with "ELSE" clauses', + lang: 'tsql', + langName: 'T-SQL', + }), + ]; +} -- 2.39.5