Browse Source

SONAR-11164 Add measures page for short-lived branches and PR

tags/7.5
Grégoire Aubert 5 years ago
parent
commit
1b6dcc529a

+ 1
- 1
server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts View File

describe('parseQuery', () => { describe('parseQuery', () => {
it('should correctly parse the url query', () => { it('should correctly parse the url query', () => {
expect(utils.parseQuery({})).toEqual({ expect(utils.parseQuery({})).toEqual({
metric: 'project_overview',
metric: utils.DEFAULT_METRIC,
selected: '', selected: '',
view: utils.DEFAULT_VIEW view: utils.DEFAULT_VIEW
}); });

+ 96
- 50
server/sonar-web/src/main/js/apps/component-measures/components/App.tsx View File

import { InjectedRouter } from 'react-router'; import { InjectedRouter } from 'react-router';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import MeasureContentContainer from './MeasureContentContainer'; import MeasureContentContainer from './MeasureContentContainer';
import MeasuresEmpty from './MeasuresEmpty';
import MeasureOverviewContainer from './MeasureOverviewContainer'; import MeasureOverviewContainer from './MeasureOverviewContainer';
import Sidebar from '../sidebar/Sidebar'; import Sidebar from '../sidebar/Sidebar';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import { isProjectOverview, hasBubbleChart, parseQuery, serializeQuery, Query } from '../utils';
import {
isProjectOverview,
hasBubbleChart,
parseQuery,
serializeQuery,
Query,
hasFullMeasures,
getMeasuresPageMetricKeys,
groupByDomains,
sortMeasures
} from '../utils';
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { import {
translateWithParameters, translateWithParameters,
translate translate
} from '../../../helpers/l10n'; } from '../../../helpers/l10n';
import { getDisplayMetrics } from '../../../helpers/measures';
import { RawQuery } from '../../../helpers/query'; import { RawQuery } from '../../../helpers/query';
import { import {
BranchLike, BranchLike,
ComponentMeasure, ComponentMeasure,
CurrentUser,
MeasureEnhanced, MeasureEnhanced,
Metric, Metric,
CurrentUser,
Period Period
} from '../../../app/types'; } from '../../../app/types';
import '../../../components/search-navigator.css'; import '../../../components/search-navigator.css';


fetchMeasures = ({ branchLike, component, fetchMeasures, metrics }: Props) => { fetchMeasures = ({ branchLike, component, fetchMeasures, metrics }: Props) => {
this.setState({ loading: true }); this.setState({ loading: true });
const filteredKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key);


const filteredKeys = getMeasuresPageMetricKeys(metrics, branchLike);
fetchMeasures(component.key, filteredKeys, branchLike).then( fetchMeasures(component.key, filteredKeys, branchLike).then(
({ measures, leakPeriod }) => { ({ measures, leakPeriod }) => {
if (this.mounted) { if (this.mounted) {
); );
}; };


getHelmetTitle = (query: Query, displayOverview: boolean, metric?: Metric) => {
if (displayOverview && query.metric) {
return isProjectOverview(query.metric)
? translate('component_measures.overview.project_overview.facet')
: translateWithParameters(
'component_measures.domain_x_overview',
getLocalizedMetricDomain(query.metric)
);
}
return metric ? metric.name : translate('layout.measures');
};

getSelectedMetric = (query: Query, displayOverview: boolean) => {
if (displayOverview) {
return undefined;
}
const metric = this.props.metrics[query.metric];
if (!metric) {
const domainMeasures = groupByDomains(this.state.measures);
const firstMeasure =
domainMeasures[0] && sortMeasures(domainMeasures[0].name, domainMeasures[0].measures)[0];
if (firstMeasure && typeof firstMeasure !== 'string') {
return firstMeasure.metric;
}
}
return metric;
};

updateQuery = (newQuery: Partial<Query>) => { updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({ const query = serializeQuery({
...parseQuery(this.props.location.query), ...parseQuery(this.props.location.query),
}); });
}; };


getHelmetTitle = (metric?: Metric) => {
if (metric && hasBubbleChart(metric.key)) {
return isProjectOverview(metric.key)
? translate('component_measures.overview.project_overview.facet')
: translateWithParameters(
'component_measures.domain_x_overview',
getLocalizedMetricDomain(metric.key)
);
renderContent = (displayOverview: boolean, query: Query, metric?: Metric) => {
const { branchLike, component, fetchMeasures, metrics } = this.props;
const { leakPeriod, measures } = this.state;

if (measures.length === 0) {
return <MeasuresEmpty />;
} }
return metric ? metric.name : translate('layout.measures');

if (displayOverview) {
return (
<MeasureOverviewContainer
branchLike={branchLike}
className="layout-page-main"
currentUser={this.props.currentUser}
domain={query.metric}
leakPeriod={leakPeriod}
metrics={metrics}
rootComponent={component}
router={this.props.router}
selected={query.selected}
updateQuery={this.updateQuery}
/>
);
}

if (!metric) {
return <MeasuresEmpty />;
}

return (
<MeasureContentContainer
branchLike={branchLike}
className="layout-page-main"
currentUser={this.props.currentUser}
fetchMeasures={fetchMeasures}
leakPeriod={leakPeriod}
metric={metric}
metrics={metrics}
rootComponent={component}
router={this.props.router}
selected={query.selected}
updateQuery={this.updateQuery}
view={query.view}
/>
);
}; };


render() { render() {
if (isLoading) { if (isLoading) {
return <i className="spinner spinner-margin" />; return <i className="spinner spinner-margin" />;
} }
const { branchLike, component, fetchMeasures, metrics } = this.props;
const { leakPeriod } = this.state;
const { branchLike } = this.props;
const { measures } = this.state;
const query = parseQuery(this.props.location.query); const query = parseQuery(this.props.location.query);
const metric = metrics[query.metric];
const hasOverview = hasFullMeasures(branchLike);
const displayOverview = hasOverview && hasBubbleChart(query.metric);
const metric = this.getSelectedMetric(query, displayOverview);
return ( return (
<div className="layout-page" id="component-measures"> <div className="layout-page" id="component-measures">
<Suggestions suggestions="component_measures" /> <Suggestions suggestions="component_measures" />
<Helmet title={this.getHelmetTitle(metric)} />
<Helmet title={this.getHelmetTitle(query, displayOverview, metric)} />


<ScreenPositionHelper className="layout-page-side-outer"> <ScreenPositionHelper className="layout-page-side-outer">
{({ top }) => ( {({ top }) => (
<div className="layout-page-side-inner"> <div className="layout-page-side-inner">
<div className="layout-page-filters"> <div className="layout-page-filters">
<Sidebar <Sidebar
measures={this.state.measures}
selectedMetric={query.metric}
hasOverview={hasOverview}
measures={measures}
selectedMetric={metric ? metric.key : query.metric}
updateQuery={this.updateQuery} updateQuery={this.updateQuery}
/> />
</div> </div>
)} )}
</ScreenPositionHelper> </ScreenPositionHelper>


{metric && (
<MeasureContentContainer
branchLike={branchLike}
className="layout-page-main"
currentUser={this.props.currentUser}
fetchMeasures={fetchMeasures}
leakPeriod={leakPeriod}
metric={metric}
metrics={metrics}
rootComponent={component}
router={this.props.router}
selected={query.selected}
updateQuery={this.updateQuery}
view={query.view}
/>
)}
{!metric &&
hasBubbleChart(query.metric) && (
<MeasureOverviewContainer
branchLike={branchLike}
className="layout-page-main"
currentUser={this.props.currentUser}
domain={query.metric}
leakPeriod={leakPeriod}
metrics={metrics}
rootComponent={component}
router={this.props.router}
selected={query.selected}
updateQuery={this.updateQuery}
/>
)}
{this.renderContent(displayOverview, query, metric)}
</div> </div>
); );
} }

+ 9
- 5
server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx View File

import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { getMeasureHistoryUrl } from '../../../helpers/urls'; import { getMeasureHistoryUrl } from '../../../helpers/urls';
import { isDiffMetric } from '../../../helpers/measures'; import { isDiffMetric } from '../../../helpers/measures';
import { MeasureEnhanced, Metric, ComponentMeasure, BranchLike, Period } from '../../../app/types';
import { BranchLike, ComponentMeasure, MeasureEnhanced, Metric, Period } from '../../../app/types';
import { hasFullMeasures } from '../utils';


interface Props { interface Props {
branchLike?: BranchLike; branchLike?: BranchLike;
export default function MeasureHeader(props: Props) { export default function MeasureHeader(props: Props) {
const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props; const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props;
const isDiff = isDiffMetric(metric.key); const isDiff = isDiffMetric(metric.key);
const hasHistory = component.qualifier !== 'FIL' && component.qualifier !== 'UTS';
const hasHistory =
component.qualifier !== 'FIL' && component.qualifier !== 'UTS' && hasFullMeasures(branchLike);
const displayLeak = hasFullMeasures(branchLike);
return ( return (
<div className="measure-details-header big-spacer-bottom"> <div className="measure-details-header big-spacer-bottom">
<div className="measure-details-primary"> <div className="measure-details-primary">
)} )}
</div> </div>
<div className="measure-details-primary-actions"> <div className="measure-details-primary-actions">
{leakPeriod && (
<LeakPeriodLegend className="spacer-left" component={component} period={leakPeriod} />
)}
{displayLeak &&
leakPeriod && (
<LeakPeriodLegend className="spacer-left" component={component} period={leakPeriod} />
)}
</div> </div>
</div> </div>
{secondaryMeasure && {secondaryMeasure &&

+ 29
- 0
server/sonar-web/src/main/js/apps/component-measures/components/MeasuresEmpty.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2018 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 { translate } from '../../../helpers/l10n';

export default function MeasuresEmpty() {
return (
<div className="layout-page-main">
<div className="note text-center">{translate('component_measures.empty')}</div>
</div>
);
}

+ 9
- 5
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx View File

import * as React from 'react'; import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import App from '../App'; import App from '../App';
import { waitAndUpdate } from '../../../../helpers/testUtils';


const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' }; const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' };


component: COMPONENT, component: COMPONENT,
currentUser: { isLoggedIn: false }, currentUser: { isLoggedIn: false },
location: { pathname: '/component_measures', query: { metric: 'coverage' } }, location: { pathname: '/component_measures', query: { metric: 'coverage' } },
fetchMeasures: jest.fn().mockResolvedValue({ component: COMPONENT, measures: [] }),
fetchMeasures: jest.fn().mockResolvedValue({
component: COMPONENT,
measures: [{ metric: 'coverage', value: '80.0' }]
}),
fetchMetrics: jest.fn(), fetchMetrics: jest.fn(),
metrics: METRICS, metrics: METRICS,
metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'], metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'],
router: { push: jest.fn() } as any router: { push: jest.fn() } as any
}; };


it('should render correctly', () => {
it('should render correctly', async () => {
const wrapper = shallow(<App {...PROPS} />); const wrapper = shallow(<App {...PROPS} />);
expect(wrapper.find('.spinner')).toHaveLength(1); expect(wrapper.find('.spinner')).toHaveLength(1);
wrapper.setState({ loading: false });
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });


it('should render a measure overview', () => {
it('should render a measure overview', async () => {
const wrapper = shallow( const wrapper = shallow(
<App <App
{...PROPS} {...PROPS}
/> />
); );
expect(wrapper.find('.spinner')).toHaveLength(1); expect(wrapper.find('.spinner')).toHaveLength(1);
wrapper.setState({ loading: false });
await waitAndUpdate(wrapper);
expect(wrapper.find('MeasureOverviewContainer')).toHaveLength(1); expect(wrapper.find('MeasureOverviewContainer')).toHaveLength(1);
}); });

+ 17
- 3
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx View File

).toMatchSnapshot(); ).toMatchSnapshot();
}); });


it('should render with branch', () => {
const shortBranch = { isMain: false, name: 'feature', mergeBranch: '', type: 'SHORT' };
it('should render with long living branch', () => {
const longBranch = { isMain: false, name: 'branch-6.7', type: 'LONG' };
expect( expect(
shallow(<MeasureHeader branchLike={shortBranch} {...PROPS} />).find('Link')
shallow(<MeasureHeader branchLike={longBranch} {...PROPS} />).find('Link')
).toMatchSnapshot();
});

it('should render with short living branch', () => {
const shortBranch = { isMain: false, name: 'feature', mergeBranch: 'master', type: 'SHORT' };
expect(
shallow(
<MeasureHeader
{...PROPS}
branchLike={shortBranch}
measure={LEAK_MEASURE}
metric={LEAK_METRIC}
/>
)
).toMatchSnapshot(); ).toMatchSnapshot();
}); });



+ 37
- 2
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap View File

</div> </div>
`; `;


exports[`should render with branch 1`] = `
exports[`should render with long living branch 1`] = `
<Link <Link
className="js-show-history spacer-left button button-small" className="js-show-history spacer-left button button-small"
onlyActiveOnIndex={false} onlyActiveOnIndex={false}
Object { Object {
"pathname": "/project/activity", "pathname": "/project/activity",
"query": Object { "query": Object {
"branch": "feature",
"branch": "branch-6.7",
"custom_metrics": "reliability_rating", "custom_metrics": "reliability_rating",
"graph": "custom", "graph": "custom",
"id": "foo", "id": "foo",
</Link> </Link>
`; `;


exports[`should render with short living branch 1`] = `
<div
className="measure-details-header big-spacer-bottom"
>
<div
className="measure-details-primary"
>
<div
className="measure-details-metric"
>
<IssueTypeIcon
className="little-spacer-right text-text-bottom"
query="new_reliability_rating"
/>
Reliability Rating on New Code
<span
className="measure-details-value spacer-left"
>
<strong>
<Measure
className="domain-measures-leak"
metricKey="new_reliability_rating"
metricType="RATING"
value="3.0"
/>
</strong>
</span>
</div>
<div
className="measure-details-primary-actions"
/>
</div>
</div>
`;

exports[`should work with measure without value 1`] = ` exports[`should work with measure without value 1`] = `
<div <div
className="measure-details-header big-spacer-bottom" className="measure-details-header big-spacer-bottom"

+ 15
- 10
server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx View File



interface Props { interface Props {
domain: { name: string; measures: MeasureEnhanced[] }; domain: { name: string; measures: MeasureEnhanced[] };
hasOverview: boolean;
onChange: (metric: string) => void; onChange: (metric: string) => void;
onToggle: (property: string) => void; onToggle: (property: string) => void;
open: boolean; open: boolean;
} }


export default class DomainFacet extends React.PureComponent<Props> { export default class DomainFacet extends React.PureComponent<Props> {
getValues = () => {
const { domain, selected } = this.props;
const measureSelected = domain.measures.find(measure => measure.metric.key === selected);
const overviewSelected = domain.name === selected && this.hasOverview(domain.name);
if (measureSelected) {
return [getLocalizedMetricName(measureSelected.metric)];
}
return overviewSelected ? [translate('component_measures.domain_overview')] : [];
};

handleHeaderClick = () => { handleHeaderClick = () => {
this.props.onToggle(this.props.domain.name); this.props.onToggle(this.props.domain.name);
}; };


hasFacetSelected = (domain: { name: string }, measures: MeasureEnhanced[], selected: string) => { hasFacetSelected = (domain: { name: string }, measures: MeasureEnhanced[], selected: string) => {
const measureSelected = measures.find(measure => measure.metric.key === selected); const measureSelected = measures.find(measure => measure.metric.key === selected);
const overviewSelected = domain.name === selected && hasBubbleChart(domain.name);
const overviewSelected = domain.name === selected && this.hasOverview(domain.name);
return measureSelected || overviewSelected; return measureSelected || overviewSelected;
}; };


getValues = () => {
const { domain, selected } = this.props;
const measureSelected = domain.measures.find(measure => measure.metric.key === selected);
const overviewSelected = domain.name === selected && hasBubbleChart(domain.name);
if (measureSelected) {
return [getLocalizedMetricName(measureSelected.metric)];
}
return overviewSelected ? [translate('component_measures.domain_overview')] : [];
hasOverview = (domain: string) => {
return this.props.hasOverview && hasBubbleChart(domain);
}; };


renderItemFacetStat = (item: MeasureEnhanced) => { renderItemFacetStat = (item: MeasureEnhanced) => {


renderOverviewFacet = () => { renderOverviewFacet = () => {
const { domain, selected } = this.props; const { domain, selected } = this.props;
if (!hasBubbleChart(domain.name)) {
if (!this.hasOverview(domain.name)) {
return null; return null;
} }
return ( return (

+ 11
- 6
server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx View File

import { MeasureEnhanced } from '../../../app/types'; import { MeasureEnhanced } from '../../../app/types';


interface Props { interface Props {
hasOverview: boolean;
measures: MeasureEnhanced[]; measures: MeasureEnhanced[];
selectedMetric: string; selectedMetric: string;
updateQuery: (query: Query) => void;
updateQuery: (query: Partial<Query>) => void;
} }


interface State { interface State {
this.props.updateQuery({ metric, ...this.resetSelection(metric) }); this.props.updateQuery({ metric, ...this.resetSelection(metric) });


render() { render() {
const { hasOverview } = this.props;
return ( return (
<div> <div>
<ProjectOverviewFacet
onChange={this.changeMetric}
selected={this.props.selectedMetric}
value={PROJECT_OVERVEW}
/>
{hasOverview && (
<ProjectOverviewFacet
onChange={this.changeMetric}
selected={this.props.selectedMetric}
value={PROJECT_OVERVEW}
/>
)}
{groupByDomains(this.props.measures).map(domain => ( {groupByDomains(this.props.measures).map(domain => (
<DomainFacet <DomainFacet
domain={domain} domain={domain}
hasOverview={hasOverview}
key={domain.name} key={domain.name}
onChange={this.changeMetric} onChange={this.changeMetric}
onToggle={this.toggleFacet} onToggle={this.toggleFacet}

+ 14
- 23
server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-test.tsx View File

}; };


const PROPS = { const PROPS = {
domain: DOMAIN,
hasOverview: true,
onChange: () => {}, onChange: () => {},
onToggle: () => {}, onToggle: () => {},
open: true, open: true,
domain: DOMAIN,
selected: 'foo' selected: 'foo'
}; };


expect(wrapper.find('FacetItemsList')).toHaveLength(0); expect(wrapper.find('FacetItemsList')).toHaveLength(0);
}); });


it('should render without overview', () => {
const wrapper = shallow(<DomainFacet {...PROPS} hasOverview={false} />);
expect(
wrapper
.find('FacetItem')
.filterWhere(node => node.getElement().key === 'Reliability')
.exists()
).toBe(false);
});

it('should not display subtitles of new measures if there is none', () => { it('should not display subtitles of new measures if there is none', () => {
const domain = { const domain = {
name: 'Reliability', name: 'Reliability',
] ]
}; };


expect(
shallow(
<DomainFacet
domain={domain}
onChange={() => {}}
onToggle={() => {}}
open={true}
selected={'foo'}
/>
)
).toMatchSnapshot();
expect(shallow(<DomainFacet {...PROPS} domain={domain} />)).toMatchSnapshot();
}); });


it('should not display subtitles of new measures if there is none, even on last line', () => { it('should not display subtitles of new measures if there is none, even on last line', () => {
] ]
}; };


expect(
shallow(
<DomainFacet
domain={domain}
onChange={() => {}}
onToggle={() => {}}
open={true}
selected={'foo'}
/>
)
).toMatchSnapshot();
expect(shallow(<DomainFacet {...PROPS} domain={domain} />)).toMatchSnapshot();
}); });

+ 1
- 0
server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-test.tsx View File

]; ];


const PROPS = { const PROPS = {
hasOverview: true,
measures: MEASURES, measures: MEASURES,
selectedMetric: 'duplicated_lines_density', selectedMetric: 'duplicated_lines_density',
updateQuery: () => {} updateQuery: () => {}

+ 2
- 0
server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap View File

"name": "Coverage", "name": "Coverage",
} }
} }
hasOverview={true}
key="Coverage" key="Coverage"
onChange={[Function]} onChange={[Function]}
onToggle={[Function]} onToggle={[Function]}
"name": "Duplications", "name": "Duplications",
} }
} }
hasOverview={true}
key="Duplications" key="Duplications"
onChange={[Function]} onChange={[Function]}
onToggle={[Function]} onToggle={[Function]}

+ 26
- 3
server/sonar-web/src/main/js/apps/component-measures/utils.ts View File

ComponentMeasure, ComponentMeasure,
ComponentMeasureEnhanced, ComponentMeasureEnhanced,
Metric, Metric,
MeasureEnhanced
MeasureEnhanced,
BranchLike
} from '../../app/types'; } from '../../app/types';
import { enhanceMeasure } from '../../components/measure/utils'; import { enhanceMeasure } from '../../components/measure/utils';
import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query'; import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
import { isLongLivingBranch, isMainBranch } from '../../helpers/branches';
import { getDisplayMetrics } from '../../helpers/measures';


export const PROJECT_OVERVEW = 'project_overview'; export const PROJECT_OVERVEW = 'project_overview';
export const DEFAULT_VIEW = 'list'; export const DEFAULT_VIEW = 'list';
} }


export function hasBubbleChart(domainName: string): boolean { export function hasBubbleChart(domainName: string): boolean {
return bubbles[domainName] != null;
return bubbles[domainName] !== undefined;
} }


export function hasFacetStat(metric: string): boolean { export function hasFacetStat(metric: string): boolean {
return metric !== 'alert_status'; return metric !== 'alert_status';
} }


export function hasFullMeasures(branch?: BranchLike) {
return !branch || isLongLivingBranch(branch) || isMainBranch(branch);
}

export function getMeasuresPageMetricKeys(metrics: { [key: string]: Metric }, branch?: BranchLike) {
if (!hasFullMeasures(branch)) {
return [
'new_coverage',
'new_lines_to_cover',
'new_uncovered_lines',
'new_line_coverage',
'new_conditions_to_cover',
'new_uncovered_conditions',
'new_branch_coverage'
];
}

return getDisplayMetrics(Object.values(metrics)).map(metric => metric.key);
}

export function getBubbleMetrics(domain: string, metrics: { [key: string]: Metric }) { export function getBubbleMetrics(domain: string, metrics: { [key: string]: Metric }) {
const conf = bubbles[domain]; const conf = bubbles[domain];
return { return {
}; };


export interface Query { export interface Query {
metric?: string;
metric: string;
selected?: string; selected?: string;
view: string; view: string;
} }

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

component_measures.legend.worse_of_x_y=Worse of {0} and {1} component_measures.legend.worse_of_x_y=Worse of {0} and {1}
component_measures.no_history=There is no historical data. component_measures.no_history=There is no historical data.
component_measures.not_found=The requested measure was not found. component_measures.not_found=The requested measure was not found.
component_measures.empty=No measures.
component_measures.to_select_files=to select files component_measures.to_select_files=to select files
component_measures.to_navigate=to navigate component_measures.to_navigate=to navigate
component_measures.to_navigate_files=to next/previous file component_measures.to_navigate_files=to next/previous file

Loading…
Cancel
Save