Quellcode durchsuchen

SONAR-15790 Add new/overall code filter on portfolio's projects page

tags/9.3.0.51899
Mathieu Suen vor 2 Jahren
Ursprung
Commit
2da4d0ec8c

+ 36
- 0
server/sonar-web/src/main/js/apps/code/__tests__/__snapshots__/utils-test.tsx.snap Datei anzeigen

@@ -15,6 +15,12 @@ Array [

exports[`getCodeMetrics should return the right metrics for portfolios 1`] = `
Array [
"releasability_rating",
"new_reliability_rating",
"new_security_rating",
"new_security_review_rating",
"new_maintainability_rating",
"new_lines",
"releasability_rating",
"reliability_rating",
"security_rating",
@@ -25,6 +31,36 @@ Array [
`;

exports[`getCodeMetrics should return the right metrics for portfolios 2`] = `
Array [
"releasability_rating",
"new_reliability_rating",
"new_security_rating",
"new_security_review_rating",
"new_maintainability_rating",
"new_lines",
"releasability_rating",
"reliability_rating",
"security_rating",
"security_review_rating",
"sqale_rating",
"ncloc",
"alert_status",
]
`;

exports[`getCodeMetrics should return the right metrics for portfolios 3`] = `
Array [
"releasability_rating",
"new_reliability_rating",
"new_security_rating",
"new_security_review_rating",
"new_maintainability_rating",
"new_lines",
"alert_status",
]
`;

exports[`getCodeMetrics should return the right metrics for portfolios 4`] = `
Array [
"releasability_rating",
"reliability_rating",

+ 6
- 0
server/sonar-web/src/main/js/apps/code/__tests__/utils-test.tsx Datei anzeigen

@@ -55,6 +55,12 @@ describe('getCodeMetrics', () => {
it('should return the right metrics for portfolios', () => {
expect(getCodeMetrics('VW')).toMatchSnapshot();
expect(getCodeMetrics('VW', undefined, { includeQGStatus: true })).toMatchSnapshot();
expect(
getCodeMetrics('VW', undefined, { includeQGStatus: true, newCode: true })
).toMatchSnapshot();
expect(
getCodeMetrics('VW', undefined, { includeQGStatus: true, newCode: false })
).toMatchSnapshot();
});

it('should return the right metrics for apps', () => {

+ 1
- 0
server/sonar-web/src/main/js/apps/code/code.css Datei anzeigen

@@ -76,6 +76,7 @@

.code-search {
margin-bottom: 10px;
display: flex;
}

.code-components-header {

+ 26
- 6
server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx Datei anzeigen

@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import { Location } from 'history';
import { debounce } from 'lodash';
import { debounce, intersection } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { connect } from 'react-redux';
@@ -35,7 +35,12 @@ import { getMetrics } from '../../../store/rootReducer';
import { BranchLike } from '../../../types/branch-like';
import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket';
import '../code.css';
import { loadMoreChildren, retrieveComponent, retrieveComponentChildren } from '../utils';
import {
getCodeMetrics,
loadMoreChildren,
retrieveComponent,
retrieveComponentChildren
} from '../utils';
import Breadcrumbs from './Breadcrumbs';
import Components from './Components';
import Search from './Search';
@@ -69,9 +74,10 @@ interface State {
searchResults?: T.ComponentMeasure[];
sourceViewer?: T.ComponentMeasure;
total: number;
newCodeSelected: boolean;
}

export class CodeApp extends React.PureComponent<Props, State> {
export class CodeApp extends React.Component<Props, State> {
mounted = false;
state: State;

@@ -81,7 +87,8 @@ export class CodeApp extends React.PureComponent<Props, State> {
breadcrumbs: [],
loading: true,
page: 0,
total: 0
total: 0,
newCodeSelected: true
};
this.refreshBranchStatus = debounce(this.refreshBranchStatus, 1000);
}
@@ -225,6 +232,10 @@ export class CodeApp extends React.PureComponent<Props, State> {
this.setState({ highlighted: undefined });
};

handleSelectNewCode = (newCodeSelected: boolean) => {
this.setState({ newCodeSelected });
};

handleUpdate = () => {
const { component, location } = this.props;
const { selected } = location.query;
@@ -248,6 +259,7 @@ export class CodeApp extends React.PureComponent<Props, State> {
components = [],
highlighted,
loading,
newCodeSelected,
total,
searchResults,
sourceViewer
@@ -266,6 +278,12 @@ export class CodeApp extends React.PureComponent<Props, State> {
'search-results': showSearch
});

const metricKeys = intersection(
getCodeMetrics(component.qualifier, branchLike, { newCode: newCodeSelected }),
Object.keys(this.props.metrics)
);
const metrics = metricKeys.map(metric => this.props.metrics[metric]);

const defaultTitle =
baseComponent && ['APP', 'VW', 'SVW'].includes(baseComponent.qualifier)
? translate('projects.page')
@@ -284,6 +302,8 @@ export class CodeApp extends React.PureComponent<Props, State> {
<Search
branchLike={branchLike}
component={component}
newCodeSelected={newCodeSelected}
onNewCodeToggle={this.handleSelectNewCode}
onSearchClear={this.handleSearchClear}
onSearchResults={this.handleSearchResults}
/>
@@ -316,7 +336,7 @@ export class CodeApp extends React.PureComponent<Props, State> {
branchLike={branchLike}
components={components}
cycle={true}
metrics={this.props.metrics}
metrics={metrics}
onEndOfList={this.handleLoadMore}
onGoToParent={this.handleGoToParent}
onHighlight={this.handleHighlight}
@@ -334,7 +354,7 @@ export class CodeApp extends React.PureComponent<Props, State> {
<Components
branchLike={this.props.branchLike}
components={searchResults}
metrics={{}}
metrics={[]}
onHighlight={this.handleHighlight}
onSelect={this.handleSelect}
rootComponent={component}

+ 7
- 12
server/sonar-web/src/main/js/apps/code/components/Components.tsx Datei anzeigen

@@ -17,12 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { intersection, sortBy } from 'lodash';
import { sortBy } from 'lodash';
import * as React from 'react';
import withKeyboardNavigation from '../../../components/hoc/withKeyboardNavigation';
import { getComponentMeasureUniqueKey } from '../../../helpers/component';
import { BranchLike } from '../../../types/branch-like';
import { getCodeMetrics } from '../utils';
import Component from './Component';
import ComponentsEmpty from './ComponentsEmpty';
import ComponentsHeader from './ComponentsHeader';
@@ -31,7 +30,7 @@ interface Props {
baseComponent?: T.ComponentMeasure;
branchLike?: BranchLike;
components: T.ComponentMeasure[];
metrics: T.Dict<T.Metric>;
metrics: T.Metric[];
rootComponent: T.ComponentMeasure;
selected?: T.ComponentMeasure;
}
@@ -41,12 +40,8 @@ const BASE_COLUMN_COUNT = 4;
export class Components extends React.PureComponent<Props> {
render() {
const { baseComponent, branchLike, components, rootComponent, selected } = this.props;
const metricKeys = intersection(
getCodeMetrics(rootComponent.qualifier, branchLike),
Object.keys(this.props.metrics)
);
const metrics = metricKeys.map(metric => this.props.metrics[metric]);
const colSpan = metrics.length + BASE_COLUMN_COUNT;

const colSpan = this.props.metrics.length + BASE_COLUMN_COUNT;
const canBePinned = baseComponent && !['APP', 'VW', 'SVW'].includes(baseComponent.qualifier);

return (
@@ -55,7 +50,7 @@ export class Components extends React.PureComponent<Props> {
<ComponentsHeader
baseComponent={baseComponent}
canBePinned={canBePinned}
metrics={metricKeys}
metrics={this.props.metrics.map(metric => metric.key)}
rootComponent={rootComponent}
/>
)}
@@ -68,7 +63,7 @@ export class Components extends React.PureComponent<Props> {
component={baseComponent}
hasBaseComponent={false}
key={baseComponent.key}
metrics={metrics}
metrics={this.props.metrics}
rootComponent={rootComponent}
/>
<tr className="blank">
@@ -96,7 +91,7 @@ export class Components extends React.PureComponent<Props> {
component={component}
hasBaseComponent={baseComponent !== undefined}
key={getComponentMeasureUniqueKey(component)}
metrics={metrics}
metrics={this.props.metrics}
previous={index > 0 ? list[index - 1] : undefined}
rootComponent={rootComponent}
selected={

+ 47
- 0
server/sonar-web/src/main/js/apps/code/components/PortfolioNewCodeToggle.tsx Datei anzeigen

@@ -0,0 +1,47 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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 * as React from 'react';
import { Button } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';

export interface PortfolioNewCodeToggleProps {
showNewCode: boolean;
onNewCodeToggle: (newSelected: boolean) => void;
}

export default function PortfolioNewCodeToggle(props: PortfolioNewCodeToggleProps) {
const { showNewCode } = props;
return (
<div className="big-spacer-right">
<div className="button-group">
<Button
className={showNewCode ? 'button-active' : undefined}
onClick={() => props.onNewCodeToggle(true)}>
{translate('projects.view.new_code')}
</Button>
<Button
className={showNewCode ? undefined : 'button-active'}
onClick={() => props.onNewCodeToggle(false)}>
{translate('projects.view.overall_code')}
</Button>
</div>
</div>
);
}

+ 14
- 2
server/sonar-web/src/main/js/apps/code/components/Search.tsx Datei anzeigen

@@ -26,12 +26,15 @@ import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { BranchLike } from '../../../types/branch-like';
import PortfolioNewCodeToggle from './PortfolioNewCodeToggle';

interface Props {
branchLike?: BranchLike;
component: T.ComponentMeasure;
location: Location;
newCodeSelected: boolean;
onSearchClear: () => void;
onNewCodeToggle: (newCode: boolean) => void;
onSearchResults: (results?: T.ComponentMeasure[]) => void;
router: Router;
}
@@ -85,7 +88,10 @@ export class Search extends React.PureComponent<Props, State> {
if (this.mounted) {
const { branchLike, component, router, location } = this.props;
this.setState({ loading: true });
router.replace({ pathname: location.pathname, query: { ...location.query, search: query } });
router.replace({
pathname: location.pathname,
query: { ...location.query, search: query }
});

const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);
const qualifiers = isPortfolio ? 'SVW,TRK' : 'BRC,UTS,FIL';
@@ -125,12 +131,18 @@ export class Search extends React.PureComponent<Props, State> {
};

render() {
const { component } = this.props;
const { component, newCodeSelected } = this.props;
const { loading } = this.state;
const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);

return (
<div className="code-search" id="code-search">
{isPortfolio && (
<PortfolioNewCodeToggle
onNewCodeToggle={this.props.onNewCodeToggle}
showNewCode={newCodeSelected}
/>
)}
<SearchBox
minLength={3}
onChange={this.handleQueryChange}

+ 53
- 11
server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx Datei anzeigen

@@ -27,17 +27,21 @@ import { ComponentQualifier } from '../../../../types/component';
import { loadMoreChildren, retrieveComponent } from '../../utils';
import { CodeApp } from '../CodeApp';

jest.mock('../../utils', () => ({
loadMoreChildren: jest.fn().mockResolvedValue({}),
retrieveComponent: jest.fn().mockResolvedValue({
breadcrumbs: [],
component: { qualifier: 'APP' },
components: [],
page: 0,
total: 1
}),
retrieveComponentChildren: () => Promise.resolve()
}));
jest.mock('../../utils', () => {
const { getCodeMetrics } = jest.requireActual('../../utils');
return {
getCodeMetrics,
loadMoreChildren: jest.fn().mockResolvedValue({}),
retrieveComponent: jest.fn().mockResolvedValue({
breadcrumbs: [],
component: { qualifier: 'APP' },
components: [],
page: 0,
total: 1
}),
retrieveComponentChildren: () => Promise.resolve()
};
});

const METRICS = {
coverage: { id: '2', key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' },
@@ -155,6 +159,44 @@ it('should handle go to parent correctly', async () => {
});
});

it('should correcly display new/overall measure for portfolio', async () => {
const component1 = mockComponent({ qualifier: ComponentQualifier.Project });
const metrics = {
reliability_rating: {
id: '2',
key: 'reliability_rating',
type: 'RATING',
name: 'reliability_rating',
domain: 'reliability_rating'
},
new_reliability_rating: {
id: '4',
key: 'new_reliability_rating',
type: 'RATING',
name: 'new_reliability_rating',
domain: 'new_reliability_rating'
}
};
(retrieveComponent as jest.Mock<any>).mockResolvedValueOnce({
breadcrumbs: [],
component: mockComponent(),
components: [component1],
page: 0,
total: 1
});

const wrapper = shallowRender({
component: mockComponent({ qualifier: ComponentQualifier.Portfolio }),
metrics
});
await waitAndUpdate(wrapper);
expect(wrapper.find('withKeyboardNavigation(Components)').props()).toMatchSnapshot('new metrics');
wrapper.setState({ newCodeSelected: false });
expect(wrapper.find('withKeyboardNavigation(Components)').props()).toMatchSnapshot(
'overall metrics'
);
});

it('should handle select correctly', () => {
const router = mockRouter();
const wrapper = shallowRender({ router });

+ 2
- 2
server/sonar-web/src/main/js/apps/code/components/__tests__/Components-test.tsx Datei anzeigen

@@ -30,7 +30,7 @@ const COMPONENT = {
branch: 'develop'
};
const PORTFOLIO = { key: 'bar', name: 'Bar', qualifier: ComponentQualifier.Portfolio };
const METRICS = { coverage: { id: '1', key: 'coverage', type: 'PERCENT', name: 'Coverage' } };
const METRICS = [{ id: '1', key: 'coverage', type: 'PERCENT', name: 'Coverage' }];
const BRANCH = mockBranch({ name: 'feature' });

it('renders correctly', () => {
@@ -48,7 +48,7 @@ it('renders correctly', () => {

it('renders correctly for a search', () => {
expect(
shallow(<Components components={[COMPONENT]} metrics={{}} rootComponent={COMPONENT} />)
shallow(<Components components={[COMPONENT]} metrics={[]} rootComponent={COMPONENT} />)
).toMatchSnapshot();
});


+ 51
- 0
server/sonar-web/src/main/js/apps/code/components/__tests__/PortfolioNewCodeToggle-test.tsx Datei anzeigen

@@ -0,0 +1,51 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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 { shallow } from 'enzyme';
import * as React from 'react';
import { Button } from '../../../../components/controls/buttons';
import PortfolioNewCodeToggle, { PortfolioNewCodeToggleProps } from '../PortfolioNewCodeToggle';

it('renders correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should toggle correctly', () => {
const onNewCodeToggle = jest.fn();
const wrapper = shallowRender({ onNewCodeToggle });
wrapper
.find(Button)
.at(1)
.simulate('click');

expect(onNewCodeToggle).toBeCalledWith(false);

wrapper
.find(Button)
.at(0)
.simulate('click');

expect(onNewCodeToggle).toBeCalledWith(true);
});

function shallowRender(props?: Partial<PortfolioNewCodeToggleProps>) {
return shallow(
<PortfolioNewCodeToggle showNewCode={true} onNewCodeToggle={jest.fn()} {...props} />
);
}

+ 6
- 0
server/sonar-web/src/main/js/apps/code/components/__tests__/Search-test.tsx Datei anzeigen

@@ -23,6 +23,7 @@ import { getTree } from '../../../../api/components';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { ComponentQualifier } from '../../../../types/component';
import { Search } from '../Search';

jest.mock('../../../../api/components', () => {
@@ -41,6 +42,9 @@ jest.mock('../../../../api/components', () => {

it('should render correcly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(
shallowRender({ component: mockComponent({ qualifier: ComponentQualifier.Portfolio }) })
).toMatchSnapshot('node code toggle for portfolio');
});

it('should search correct query on mount', async () => {
@@ -100,10 +104,12 @@ it('should handle search correctly', async () => {
function shallowRender(props?: Partial<Search['props']>) {
return shallow<Search>(
<Search
newCodeSelected={false}
component={mockComponent()}
location={mockLocation()}
onSearchClear={jest.fn()}
onSearchResults={jest.fn()}
onNewCodeToggle={jest.fn()}
router={mockRouter()}
{...props}
/>

+ 194
- 60
server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap Datei anzeigen

@@ -1,5 +1,171 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should correcly display new/overall measure for portfolio: new metrics 1`] = `
Object {
"baseComponent": 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 [],
},
"branchLike": undefined,
"components": Array [
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 [],
},
],
"cycle": true,
"metrics": Array [
Object {
"domain": "new_reliability_rating",
"id": "4",
"key": "new_reliability_rating",
"name": "new_reliability_rating",
"type": "RATING",
},
],
"onEndOfList": [Function],
"onGoToParent": [Function],
"onHighlight": [Function],
"onSelect": [Function],
"rootComponent": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"qualifier": "VW",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"selected": undefined,
}
`;

exports[`should correcly display new/overall measure for portfolio: overall metrics 1`] = `
Object {
"baseComponent": 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 [],
},
"branchLike": undefined,
"components": Array [
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 [],
},
],
"cycle": true,
"metrics": Array [
Object {
"domain": "reliability_rating",
"id": "2",
"key": "reliability_rating",
"name": "reliability_rating",
"type": "RATING",
},
],
"onEndOfList": [Function],
"onGoToParent": [Function],
"onHighlight": [Function],
"onSelect": [Function],
"rootComponent": Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"qualifier": "VW",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
},
"selected": undefined,
}
`;

exports[`should render correclty when no sub component for APP 1`] = `
<div
className="page page-limited"
@@ -55,6 +221,8 @@ exports[`should render correclty when no sub component for APP: no search 1`] =
"qualifier": "APP",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -66,7 +234,7 @@ exports[`should render correclty when no sub component for APP: no search 1`] =
>
<withKeyboardNavigation(Components)
components={Array []}
metrics={Object {}}
metrics={Array []}
onHighlight={[Function]}
onSelect={[Function]}
rootComponent={
@@ -107,6 +275,8 @@ exports[`should render correclty when no sub component for APP: with sub compone
"qualifier": "APP",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -151,22 +321,15 @@ exports[`should render correclty when no sub component for APP: with sub compone
}
cycle={true}
metrics={
Object {
"coverage": Object {
Array [
Object {
"domain": "Coverage",
"id": "2",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"new_bugs": Object {
"domain": "Reliability",
"id": "4",
"key": "new_bugs",
"name": "New Bugs",
"type": "INT",
},
}
]
}
onEndOfList={[Function]}
onGoToParent={[Function]}
@@ -246,6 +409,8 @@ exports[`should render correclty when no sub component for SVW: no search 1`] =
"qualifier": "SVW",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -257,7 +422,7 @@ exports[`should render correclty when no sub component for SVW: no search 1`] =
>
<withKeyboardNavigation(Components)
components={Array []}
metrics={Object {}}
metrics={Array []}
onHighlight={[Function]}
onSelect={[Function]}
rootComponent={
@@ -298,6 +463,8 @@ exports[`should render correclty when no sub component for SVW: with sub compone
"qualifier": "SVW",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -341,24 +508,7 @@ exports[`should render correclty when no sub component for SVW: with sub compone
]
}
cycle={true}
metrics={
Object {
"coverage": Object {
"domain": "Coverage",
"id": "2",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"new_bugs": Object {
"domain": "Reliability",
"id": "4",
"key": "new_bugs",
"name": "New Bugs",
"type": "INT",
},
}
}
metrics={Array []}
onEndOfList={[Function]}
onGoToParent={[Function]}
onHighlight={[Function]}
@@ -437,6 +587,8 @@ exports[`should render correclty when no sub component for TRK: no search 1`] =
"qualifier": "TRK",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -448,7 +600,7 @@ exports[`should render correclty when no sub component for TRK: no search 1`] =
>
<withKeyboardNavigation(Components)
components={Array []}
metrics={Object {}}
metrics={Array []}
onHighlight={[Function]}
onSelect={[Function]}
rootComponent={
@@ -489,6 +641,8 @@ exports[`should render correclty when no sub component for TRK: with sub compone
"qualifier": "TRK",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -533,22 +687,15 @@ exports[`should render correclty when no sub component for TRK: with sub compone
}
cycle={true}
metrics={
Object {
"coverage": Object {
Array [
Object {
"domain": "Coverage",
"id": "2",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"new_bugs": Object {
"domain": "Reliability",
"id": "4",
"key": "new_bugs",
"name": "New Bugs",
"type": "INT",
},
}
]
}
onEndOfList={[Function]}
onGoToParent={[Function]}
@@ -628,6 +775,8 @@ exports[`should render correclty when no sub component for VW: no search 1`] = `
"qualifier": "VW",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -639,7 +788,7 @@ exports[`should render correclty when no sub component for VW: no search 1`] = `
>
<withKeyboardNavigation(Components)
components={Array []}
metrics={Object {}}
metrics={Array []}
onHighlight={[Function]}
onSelect={[Function]}
rootComponent={
@@ -680,6 +829,8 @@ exports[`should render correclty when no sub component for VW: with sub componen
"qualifier": "VW",
}
}
newCodeSelected={true}
onNewCodeToggle={[Function]}
onSearchClear={[Function]}
onSearchResults={[Function]}
/>
@@ -723,24 +874,7 @@ exports[`should render correclty when no sub component for VW: with sub componen
]
}
cycle={true}
metrics={
Object {
"coverage": Object {
"domain": "Coverage",
"id": "2",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"new_bugs": Object {
"domain": "Reliability",
"id": "4",
"key": "new_bugs",
"name": "New Bugs",
"type": "INT",
},
}
}
metrics={Array []}
onEndOfList={[Function]}
onGoToParent={[Function]}
onHighlight={[Function]}

+ 23
- 0
server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/PortfolioNewCodeToggle-test.tsx.snap Datei anzeigen

@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders correctly 1`] = `
<div
className="big-spacer-right"
>
<div
className="button-group"
>
<Button
className="button-active"
onClick={[Function]}
>
projects.view.new_code
</Button>
<Button
onClick={[Function]}
>
projects.view.overall_code
</Button>
</div>
</div>
`;

+ 23
- 0
server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Search-test.tsx.snap Datei anzeigen

@@ -18,3 +18,26 @@ exports[`should render correcly 1`] = `
/>
</div>
`;

exports[`should render correcly: node code toggle for portfolio 1`] = `
<div
className="code-search"
id="code-search"
>
<PortfolioNewCodeToggle
onNewCodeToggle={[MockFunction]}
showNewCode={false}
/>
<SearchBox
minLength={3}
onChange={[Function]}
onKeyDown={[Function]}
placeholder="code.search_placeholder.portfolio"
value=""
/>
<DeferredSpinner
className="spacer-left"
loading={false}
/>
</div>
`;

+ 26
- 5
server/sonar-web/src/main/js/apps/code/utils.ts Datei anzeigen

@@ -20,6 +20,7 @@
import { getBreadcrumbs, getChildren, getComponent } from '../../api/components';
import { getBranchLikeQuery, isPullRequest } from '../../helpers/branch-like';
import { BranchLike } from '../../types/branch-like';
import { isPortfolioLike } from '../../types/component';
import { MetricKey } from '../../types/metrics';
import {
addComponent,
@@ -51,6 +52,15 @@ const PORTFOLIO_METRICS = [
MetricKey.ncloc
];

const NEW_PORTFOLIO_METRICS = [
MetricKey.releasability_rating,
MetricKey.new_reliability_rating,
MetricKey.new_security_rating,
MetricKey.new_security_review_rating,
MetricKey.new_maintainability_rating,
MetricKey.new_lines
];

const LEAK_METRICS = [
MetricKey.new_lines,
MetricKey.bugs,
@@ -104,10 +114,17 @@ function storeChildrenBreadcrumbs(parentComponentKey: string, children: T.Breadc
export function getCodeMetrics(
qualifier: string,
branchLike?: BranchLike,
options: { includeQGStatus?: boolean } = {}
options: { includeQGStatus?: boolean; newCode?: boolean } = {}
) {
if (['VW', 'SVW'].includes(qualifier)) {
const metrics = [...PORTFOLIO_METRICS];
if (isPortfolioLike(qualifier)) {
let metrics: MetricKey[] = [];
if (options?.newCode === undefined) {
metrics = [...NEW_PORTFOLIO_METRICS, ...PORTFOLIO_METRICS];
} else if (options?.newCode) {
metrics = [...NEW_PORTFOLIO_METRICS];
} else {
metrics = [...PORTFOLIO_METRICS];
}
return options.includeQGStatus ? metrics.concat(MetricKey.alert_status) : metrics;
}
if (qualifier === 'APP') {
@@ -159,7 +176,9 @@ export function retrieveComponentChildren(
});
}

const metrics = getCodeMetrics(qualifier, branchLike, { includeQGStatus: true });
const metrics = getCodeMetrics(qualifier, branchLike, {
includeQGStatus: true
});

return getChildren(componentKey, metrics, {
ps: PAGE_SIZE,
@@ -231,7 +250,9 @@ export function loadMoreChildren(
instance: { mounted: boolean },
branchLike?: BranchLike
): Promise<Children> {
const metrics = getCodeMetrics(qualifier, branchLike, { includeQGStatus: true });
const metrics = getCodeMetrics(qualifier, branchLike, {
includeQGStatus: true
});

return getChildren(componentKey, metrics, {
ps: PAGE_SIZE,

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Datei anzeigen

@@ -1046,6 +1046,7 @@ projects.sorting.new_coverage=Coverage
projects.sorting.new_duplications=Duplications
projects.sorting.new_lines=New Lines
projects.view.overall=Overall Status
projects.view.overall_code=Overall Code
projects.view.new_code=New Code
projects.worse_of_reliablity_and_security=Worse of Reliability and Security
projects.visualization.risk=Risk

Laden…
Abbrechen
Speichern