@@ -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", |
@@ -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', () => { |
@@ -76,6 +76,7 @@ | |||
.code-search { | |||
margin-bottom: 10px; | |||
display: flex; | |||
} | |||
.code-components-header { |
@@ -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} |
@@ -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={ |
@@ -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> | |||
); | |||
} |
@@ -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} |
@@ -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 }); |
@@ -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(); | |||
}); | |||
@@ -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} /> | |||
); | |||
} |
@@ -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} | |||
/> |
@@ -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]} |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -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, |
@@ -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 |