From: Revanshu Paliwal Date: Wed, 8 Jun 2022 13:46:29 +0000 (+0200) Subject: SONAR-16028 Navigate to project overall code from portfolio breakdown X-Git-Tag: 9.5.0.56709~8 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=793809058c6f3772959f7731316c930b51383c3c;p=sonarqube.git SONAR-16028 Navigate to project overall code from portfolio breakdown --- diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx index 69481261394..b49a0559215 100644 --- a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx @@ -34,7 +34,7 @@ import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { Alert } from '../../../components/ui/Alert'; import { isPullRequest, isSameBranchLike } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; -import { getCodeUrl, getProjectUrl } from '../../../helpers/urls'; +import { CodeScope, getCodeUrl, getProjectUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; import { isPortfolioLike } from '../../../types/component'; import { Breadcrumb, Component, ComponentMeasure, Dict, Issue, Metric } from '../../../types/types'; @@ -217,9 +217,12 @@ export class CodeApp extends React.Component { handleSelect = (component: ComponentMeasure) => { const { branchLike, component: rootComponent } = this.props; + const { newCodeSelected } = this.state; if (component.refKey) { - this.props.router.push(getProjectUrl(component.refKey, component.branch)); + const codeType = newCodeSelected ? CodeScope.New : CodeScope.Overall; + const url = getProjectUrl(component.refKey, component.branch, codeType); + this.props.router.push(url); } else { this.props.router.push(getCodeUrl(rootComponent.key, branchLike, component.key)); } @@ -350,6 +353,7 @@ export class CodeApp extends React.Component { onSelect={this.handleSelect} rootComponent={component} selected={highlighted} + newCodeSelected={newCodeSelected} /> diff --git a/server/sonar-web/src/main/js/apps/code/components/Component.tsx b/server/sonar-web/src/main/js/apps/code/components/Component.tsx index 42654cedf37..4d6a199c662 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Component.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Component.tsx @@ -40,6 +40,7 @@ interface Props { previous?: TypeComponentMeasure; rootComponent: TypeComponentMeasure; selected?: boolean; + newCodeSelected?: boolean; } export class Component extends React.PureComponent { @@ -54,7 +55,8 @@ export class Component extends React.PureComponent { metrics, previous, rootComponent, - selected = false + selected = false, + newCodeSelected } = this.props; const isFile = @@ -91,6 +93,7 @@ export class Component extends React.PureComponent { previous={previous} rootComponent={rootComponent} unclickable={isBaseComponent} + newCodeSelected={newCodeSelected} /> diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx index c555d2e1366..fae113cae52 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx @@ -24,7 +24,7 @@ import BranchIcon from '../../../components/icons/BranchIcon'; import QualifierIcon from '../../../components/icons/QualifierIcon'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; -import { getComponentOverviewUrl } from '../../../helpers/urls'; +import { CodeScope, getComponentOverviewUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; import { ComponentQualifier, @@ -52,6 +52,7 @@ export interface Props { previous?: ComponentMeasure; rootComponent: ComponentMeasure; unclickable?: boolean; + newCodeSelected?: boolean; } export default function ComponentName({ @@ -60,7 +61,8 @@ export default function ComponentName({ unclickable = false, rootComponent, previous, - canBrowse = false + canBrowse = false, + newCodeSelected }: Props) { const ariaLabel = unclickable ? translate('code.parent_folder') : undefined; @@ -81,7 +83,8 @@ export default function ComponentName({ previous, rootComponent, unclickable, - canBrowse + canBrowse, + newCodeSelected )} {component.branch ? ( @@ -111,10 +114,11 @@ function renderNameWithIcon( previous: ComponentMeasure | undefined, rootComponent: ComponentMeasure, unclickable = false, - canBrowse = false + canBrowse = false, + newCodeSelected = true ) { const name = renderName(component, previous); - + const codeType = newCodeSelected ? CodeScope.New : CodeScope.Overall; if ( !unclickable && (isPortfolioLike(component.qualifier) || @@ -129,9 +133,14 @@ function renderNameWithIcon( return ( + to={getComponentOverviewUrl( + component.refKey || component.key, + component.qualifier, + { + branch + }, + codeType + )}> {name} ); diff --git a/server/sonar-web/src/main/js/apps/code/components/Components.tsx b/server/sonar-web/src/main/js/apps/code/components/Components.tsx index 4e47f27956a..e16cf21bcf3 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Components.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Components.tsx @@ -34,13 +34,22 @@ interface Props { metrics: Metric[]; rootComponent: ComponentMeasure; selected?: ComponentMeasure; + newCodeSelected?: boolean; } const BASE_COLUMN_COUNT = 4; export class Components extends React.PureComponent { render() { - const { baseComponent, branchLike, components, rootComponent, selected, metrics } = this.props; + const { + baseComponent, + branchLike, + components, + rootComponent, + selected, + metrics, + newCodeSelected + } = this.props; const colSpan = metrics.length + BASE_COLUMN_COUNT; const canBePinned = baseComponent && !['APP', 'VW', 'SVW'].includes(baseComponent.qualifier); @@ -67,6 +76,7 @@ export class Components extends React.PureComponent { key={baseComponent.key} metrics={metrics} rootComponent={rootComponent} + newCodeSelected={newCodeSelected} /> @@ -96,6 +106,7 @@ export class Components extends React.PureComponent { metrics={this.props.metrics} previous={index > 0 ? list[index - 1] : undefined} rootComponent={rootComponent} + newCodeSelected={newCodeSelected} selected={ selected && getComponentMeasureUniqueKey(component) === getComponentMeasureUniqueKey(selected) diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx index e74d049dd86..08af19dcb29 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx @@ -214,14 +214,22 @@ it('should handle select correctly', () => { wrapper.instance().handleSelect(mockComponentMeasure(true, { refKey: 'test' })); expect(router.push).toHaveBeenCalledWith({ pathname: '/dashboard', - query: { branch: undefined, id: 'test' } + query: { branch: undefined, id: 'test', code_scope: 'new' } }); expect(wrapper.state().highlighted).toBeUndefined(); - wrapper.instance().handleSelect(mockComponentMeasure()); + wrapper.setState({ newCodeSelected: false }); + + wrapper.instance().handleSelect(mockComponentMeasure(true, { refKey: 'test' })); expect(router.push).toHaveBeenCalledWith({ pathname: '/dashboard', - query: { branch: undefined, id: 'test' } + query: { branch: undefined, id: 'test', code_scope: 'overall' } + }); + + wrapper.instance().handleSelect(mockComponentMeasure()); + expect(router.push).toHaveBeenCalledWith({ + pathname: '/code', + query: { id: 'foo', line: undefined, selected: 'foo' } }); }); diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentName-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentName-test.tsx index 7d0d3c0ddb9..b043f9e4f51 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentName-test.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentName-test.tsx @@ -49,6 +49,8 @@ describe('#ComponentName', () => { rootComponent: mockComponentMeasure(false, { qualifier: 'APP' }) }) ).toMatchSnapshot(); + expect(shallowRender({ newCodeSelected: true })).toMatchSnapshot(); + expect(shallowRender({ newCodeSelected: false })).toMatchSnapshot(); }); it('should render correctly for dirs', () => { diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap index 3fdce8225df..56b99a9c95a 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap @@ -55,6 +55,7 @@ Object { "type": "RATING", }, ], + "newCodeSelected": true, "onEndOfList": [Function], "onGoToParent": [Function], "onHighlight": [Function], @@ -139,6 +140,7 @@ Object { "type": "RATING", }, ], + "newCodeSelected": false, "onEndOfList": [Function], "onGoToParent": [Function], "onHighlight": [Function], @@ -385,6 +387,7 @@ exports[`should render correclty when no sub component for APP: with sub compone }, ] } + newCodeSelected={true} onEndOfList={[Function]} onGoToParent={[Function]} onHighlight={[Function]} @@ -571,6 +574,7 @@ exports[`should render correclty when no sub component for SVW: with sub compone } cycle={true} metrics={Array []} + newCodeSelected={true} onEndOfList={[Function]} onGoToParent={[Function]} onHighlight={[Function]} @@ -767,6 +771,7 @@ exports[`should render correclty when no sub component for TRK: with sub compone }, ] } + newCodeSelected={true} onEndOfList={[Function]} onGoToParent={[Function]} onHighlight={[Function]} @@ -953,6 +958,7 @@ exports[`should render correclty when no sub component for VW: with sub componen } cycle={true} metrics={Array []} + newCodeSelected={true} onEndOfList={[Function]} onGoToParent={[Function]} onHighlight={[Function]} diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap index 1b2109af89b..8f38318ed18 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap @@ -237,6 +237,40 @@ foo:src/index.tsx" `; +exports[`#ComponentName should render correctly for files 6`] = ` + + + + + index.tsx + + +`; + +exports[`#ComponentName should render correctly for files 7`] = ` + + + + + index.tsx + + +`; + exports[`#ComponentName should render correctly for refs 1`] = ` isDiffMetric(m.metric.key)); const isApp = component.qualifier === ComponentQualifier.Application; const leakPeriod = isApp ? appLeak : period; + const query = parseQuery(location.query); - const [tab, selectTab] = React.useState(MeasuresPanelTabs.New); + const [tab, selectTab] = React.useState(() => { + return query.codeScope === CodeScope.Overall + ? MeasuresPanelTabs.Overall + : MeasuresPanelTabs.New; + }); const isNewCodeTab = tab === MeasuresPanelTabs.New; @@ -188,4 +196,4 @@ export function MeasuresPanel(props: MeasuresPanelProps) { ); } -export default React.memo(MeasuresPanel); +export default withRouter(React.memo(MeasuresPanel)); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx index db7b961d0c1..0c85d349392 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/MeasuresPanel-test.tsx @@ -22,15 +22,30 @@ import * as React from 'react'; import BoxedTabs from '../../../../components/controls/BoxedTabs'; import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockMeasureEnhanced, mockMetric, mockPeriod } from '../../../../helpers/testMocks'; +import { + mockLocation, + mockMeasureEnhanced, + mockMetric, + mockPeriod +} from '../../../../helpers/testMocks'; import { ComponentQualifier } from '../../../../types/component'; import { MetricKey } from '../../../../types/metrics'; -import { MeasuresPanel, MeasuresPanelProps, MeasuresPanelTabs } from '../MeasuresPanel'; +import MeasuresPanel, { MeasuresPanelProps, MeasuresPanelTabs } from '../MeasuresPanel'; + +jest.mock('react', () => { + return { + ...jest.requireActual('react'), + useEffect: jest.fn().mockImplementation(f => f()) + }; +}); it('should render correctly for projects', () => { const wrapper = shallowRender(); expect(wrapper).toMatchSnapshot(); - wrapper.find(BoxedTabs).prop('onSelect')(MeasuresPanelTabs.Overall); + wrapper + .dive() + .find(BoxedTabs) + .prop('onSelect')(MeasuresPanelTabs.Overall); expect(wrapper).toMatchSnapshot(); }); @@ -39,7 +54,10 @@ it('should render correctly for applications', () => { component: mockComponent({ qualifier: ComponentQualifier.Application }) }); expect(wrapper).toMatchSnapshot(); - wrapper.find(BoxedTabs).prop('onSelect')(MeasuresPanelTabs.Overall); + wrapper + .dive() + .find(BoxedTabs) + .prop('onSelect')(MeasuresPanelTabs.Overall); expect(wrapper).toMatchSnapshot(); }); @@ -50,7 +68,10 @@ it('should render correctly if there is no new code measures', () => { mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) }) ] }); - wrapper.find(BoxedTabs).prop('onSelect')(MeasuresPanelTabs.New); + wrapper + .dive() + .find(BoxedTabs) + .prop('onSelect')(MeasuresPanelTabs.New); expect(wrapper).toMatchSnapshot(); }); @@ -63,7 +84,10 @@ it('should render correctly if branch is misconfigured', () => { ], period: mockPeriod({ date: undefined, mode: 'REFERENCE_BRANCH', parameter: 'own-reference' }) }); - wrapper.find(BoxedTabs).prop('onSelect')(MeasuresPanelTabs.New); + wrapper + .dive() + .find(BoxedTabs) + .prop('onSelect')(MeasuresPanelTabs.New); expect(wrapper).toMatchSnapshot('hide settings'); wrapper.setProps({ component: mockComponent({ configuration: { showSettings: true } }) }); @@ -85,6 +109,22 @@ it('should render correctly if the data is still loading', () => { expect(shallowRender({ loading: true })).toMatchSnapshot(); }); +it('should render correctly when code scope is overall code', () => { + expect( + shallowRender({ + location: mockLocation({ pathname: '/dashboard', query: { code_scope: 'overall' } }) + }) + ).toMatchSnapshot(); +}); + +it('should render correctly when code scope is new code', () => { + expect( + shallowRender({ + location: mockLocation({ pathname: '/dashboard', query: { code_scope: 'new' } }) + }) + ).toMatchSnapshot(); +}); + function shallowRender(props: Partial = {}) { return shallow( = {}) { mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) }), mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_bugs }) }) ]} + location={mockLocation()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap index 2b0909eaafe..406a135f1cc 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/BranchOverviewRenderer-test.tsx.snap @@ -73,7 +73,7 @@ exports[`should render correctly: default 1`] = `
- - -
-

- overview.measures -

- -
- - - overview.new_code - -
, - }, - Object { - "key": 1, - "label":
- - overview.overall_code - -
, + -
- - - - -
-
- -
-
- -
-
-
- + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "New_coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_bugs", + "key": "new_bugs", + "name": "New_bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } +/> `; exports[`should render correctly for applications 2`] = ` -
-
-

- overview.measures -

- -
- - - overview.new_code - -
, - }, - Object { - "key": 1, - "label":
- - overview.overall_code - -
, + -
- - - - -
-
- -
- -
-
-
- -
- -
-
-
-
- + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "New_coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_bugs", + "key": "new_bugs", + "name": "New_bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } +/> `; exports[`should render correctly for projects 1`] = ` -
-
-

- overview.measures -

- -
- - - overview.new_code - -
, - }, - Object { - "key": 1, - "label":
- - overview.overall_code - -
, + -
- - - - -
-
- -
-
- -
-
-
- + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "New_coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_bugs", + "key": "new_bugs", + "name": "New_bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } +/> `; exports[`should render correctly for projects 2`] = ` -
-
-

- overview.measures -

- -
- - - overview.new_code - -
, - }, - Object { - "key": 1, - "label":
- - overview.overall_code - -
, + -
- - - - -
-
- -
- -
-
-
- -
- -
-
-
-
- + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "New_coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_bugs", + "key": "new_bugs", + "name": "New_bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } +/> `; exports[`should render correctly if branch is misconfigured: hide settings 1`] = ` -
-
-

- overview.measures -

- -
- - - overview.new_code - - -
, + - - overview.overall_code - - , + ], + "tags": Array [], + } + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", }, - ] + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } + period={ + Object { + "date": undefined, + "index": 0, + "mode": "REFERENCE_BRANCH", + "parameter": "own-reference", } - /> -
- -
- + } +/> `; exports[`should render correctly if branch is misconfigured: show settings 1`] = ` -
-
-

- overview.measures -

- -
- - - overview.new_code - - -
, + - - overview.overall_code - - , + ], + "tags": Array [], + } + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", }, - ] + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } + period={ + Object { + "date": undefined, + "index": 0, + "mode": "REFERENCE_BRANCH", + "parameter": "own-reference", } - /> -
- -
- + } +/> `; exports[`should render correctly if the data is still loading 1`] = ` -
-
-

- overview.measures -

- -
-
- -
-
+ `; exports[`should render correctly if there is no coverage 1`] = ` -
-
-

- overview.measures -

- -
- - - overview.new_code - -
, - }, - Object { - "key": 1, - "label":
- - overview.overall_code - -
, + -
- - - - -
-
- -
-
-
- + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_bugs", + "key": "new_bugs", + "name": "New_bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } +/> `; exports[`should render correctly if there is no new code measures 1`] = ` -
-
-

- overview.measures -

- -
- - - overview.new_code - -
, + - - overview.overall_code - - , + ], + "tags": Array [], + } + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", }, - ] + "value": "1.0", + }, + ] + } +/> +`; + +exports[`should render correctly when code scope is new code 1`] = ` + -
- -
- + } + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/dashboard", + "query": Object { + "code_scope": "new", + }, + "search": "", + "state": Object {}, + } + } + measures={ + Array [ + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "New_coverage", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "bugs", + "key": "bugs", + "name": "Bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + Object { + "bestValue": true, + "leak": "1", + "metric": Object { + "id": "new_bugs", + "key": "new_bugs", + "name": "New_bugs", + "type": "PERCENT", + }, + "period": Object { + "bestValue": true, + "index": 1, + "value": "1.0", + }, + "value": "1.0", + }, + ] + } +/> +`; + +exports[`should render correctly when code scope is overall code 1`] = ` + `; diff --git a/server/sonar-web/src/main/js/apps/overview/utils.ts b/server/sonar-web/src/main/js/apps/overview/utils.ts index 7b2c5312589..d3eff6be5fe 100644 --- a/server/sonar-web/src/main/js/apps/overview/utils.ts +++ b/server/sonar-web/src/main/js/apps/overview/utils.ts @@ -17,12 +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 { Query } from 'history'; +import { memoize } from 'lodash'; import CoverageRating from '../../components/ui/CoverageRating'; import DuplicationsRating from '../../components/ui/DuplicationsRating'; import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues'; import { translate } from '../../helpers/l10n'; +import { parseAsString } from '../../helpers/query'; import { IssueType } from '../../types/issues'; import { MetricKey } from '../../types/metrics'; +import { RawQuery } from '../../types/types'; export const METRICS: string[] = [ // quality gate @@ -178,3 +182,11 @@ export function getMeasurementLabelKeys(type: MeasurementType, useDiffMetric: bo labelKey: MEASUREMENTS_MAP[type].labelKey }; } + +export const parseQuery = memoize( + (urlQuery: RawQuery): Query => { + return { + codeScope: parseAsString(urlQuery['code_scope']) + }; + } +); diff --git a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts index 81cfc0f0fa8..bdcb878c1b3 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts @@ -23,6 +23,7 @@ import { IssueType } from '../../types/issues'; import { SecurityStandard } from '../../types/security'; import { mockBranch, mockMainBranch, mockPullRequest } from '../mocks/branch-like'; import { + CodeScope, convertGithubApiUrlToLink, getComponentDrilldownUrl, getComponentDrilldownUrlWithSelection, @@ -130,6 +131,32 @@ describe('#getComponentOverviewUrl', () => { query: { id: SIMPLE_COMPONENT_KEY } }); }); + it('should return correct dashboard url for a project when navigating from new code', () => { + expect( + getComponentOverviewUrl( + SIMPLE_COMPONENT_KEY, + ComponentQualifier.Project, + undefined, + CodeScope.New + ) + ).toEqual({ + pathname: '/dashboard', + query: { id: SIMPLE_COMPONENT_KEY, code_scope: 'new' } + }); + }); + it('should return correct dashboard url for a project when navigating from overall code', () => { + expect( + getComponentOverviewUrl( + SIMPLE_COMPONENT_KEY, + ComponentQualifier.Project, + undefined, + CodeScope.Overall + ) + ).toEqual({ + pathname: '/dashboard', + query: { id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' } + }); + }); it('should return a dashboard url for an app', () => { expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual({ pathname: '/dashboard', diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts index 3051feb0f29..38013879e05 100644 --- a/server/sonar-web/src/main/js/helpers/urls.ts +++ b/server/sonar-web/src/main/js/helpers/urls.ts @@ -36,16 +36,26 @@ export interface Location { query?: Dict; } +export enum CodeScope { + Overall = 'overall', + New = 'new' +} + +type CodeScopeType = CodeScope.Overall | CodeScope.New; + type Query = Location['query']; +const PROJECT_BASE_URL = '/dashboard'; + export function getComponentOverviewUrl( componentKey: string, componentQualifier: ComponentQualifier | string, - branchParameters?: BranchParameters + branchParameters?: BranchParameters, + codeScope?: CodeScopeType ) { return isPortfolioLike(componentQualifier) ? getPortfolioUrl(componentKey) - : getProjectQueryUrl(componentKey, branchParameters); + : getProjectQueryUrl(componentKey, branchParameters, codeScope); } export function getComponentAdminUrl( @@ -61,12 +71,30 @@ export function getComponentAdminUrl( } } -export function getProjectUrl(project: string, branch?: string): Location { - return { pathname: '/dashboard', query: { id: project, branch } }; +export function getProjectUrl( + project: string, + branch?: string, + codeScope?: CodeScopeType +): Location { + return { + pathname: PROJECT_BASE_URL, + query: { id: project, branch, ...(codeScope && { code_scope: codeScope }) } + }; } -export function getProjectQueryUrl(project: string, branchParameters?: BranchParameters): Location { - return { pathname: '/dashboard', query: { id: project, ...branchParameters } }; +export function getProjectQueryUrl( + project: string, + branchParameters?: BranchParameters, + codeScope?: CodeScopeType +): Location { + return { + pathname: PROJECT_BASE_URL, + query: { + id: project, + ...branchParameters, + ...(codeScope && { code_scope: codeScope }) + } + }; } export function getPortfolioUrl(key: string): Location { @@ -106,11 +134,11 @@ export function getBranchLikeUrl(project: string, branchLike?: BranchLike): Loca } export function getBranchUrl(project: string, branch: string): Location { - return { pathname: '/dashboard', query: { branch, id: project } }; + return { pathname: PROJECT_BASE_URL, query: { branch, id: project } }; } export function getPullRequestUrl(project: string, pullRequest: string): Location { - return { pathname: '/dashboard', query: { id: project, pullRequest } }; + return { pathname: PROJECT_BASE_URL, query: { id: project, pullRequest } }; } /**