@@ -102,7 +102,7 @@ describe('groupByDomains', () => { | |||
describe('parseQuery', () => { | |||
it('should correctly parse the url query', () => { | |||
expect(utils.parseQuery({})).toEqual({ | |||
metric: 'project_overview', | |||
metric: utils.DEFAULT_METRIC, | |||
selected: '', | |||
view: utils.DEFAULT_VIEW | |||
}); |
@@ -22,10 +22,21 @@ import * as key from 'keymaster'; | |||
import { InjectedRouter } from 'react-router'; | |||
import Helmet from 'react-helmet'; | |||
import MeasureContentContainer from './MeasureContentContainer'; | |||
import MeasuresEmpty from './MeasuresEmpty'; | |||
import MeasureOverviewContainer from './MeasureOverviewContainer'; | |||
import Sidebar from '../sidebar/Sidebar'; | |||
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 Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | |||
import { | |||
@@ -33,14 +44,13 @@ import { | |||
translateWithParameters, | |||
translate | |||
} from '../../../helpers/l10n'; | |||
import { getDisplayMetrics } from '../../../helpers/measures'; | |||
import { RawQuery } from '../../../helpers/query'; | |||
import { | |||
BranchLike, | |||
ComponentMeasure, | |||
CurrentUser, | |||
MeasureEnhanced, | |||
Metric, | |||
CurrentUser, | |||
Period | |||
} from '../../../app/types'; | |||
import '../../../components/search-navigator.css'; | |||
@@ -117,8 +127,8 @@ export default class App extends React.PureComponent<Props, State> { | |||
fetchMeasures = ({ branchLike, component, fetchMeasures, metrics }: Props) => { | |||
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( | |||
({ measures, leakPeriod }) => { | |||
if (this.mounted) { | |||
@@ -139,6 +149,34 @@ export default class App extends React.PureComponent<Props, State> { | |||
); | |||
}; | |||
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>) => { | |||
const query = serializeQuery({ | |||
...parseQuery(this.props.location.query), | |||
@@ -154,16 +192,51 @@ export default class App extends React.PureComponent<Props, State> { | |||
}); | |||
}; | |||
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() { | |||
@@ -171,14 +244,16 @@ export default class App extends React.PureComponent<Props, State> { | |||
if (isLoading) { | |||
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 metric = metrics[query.metric]; | |||
const hasOverview = hasFullMeasures(branchLike); | |||
const displayOverview = hasOverview && hasBubbleChart(query.metric); | |||
const metric = this.getSelectedMetric(query, displayOverview); | |||
return ( | |||
<div className="layout-page" id="component-measures"> | |||
<Suggestions suggestions="component_measures" /> | |||
<Helmet title={this.getHelmetTitle(metric)} /> | |||
<Helmet title={this.getHelmetTitle(query, displayOverview, metric)} /> | |||
<ScreenPositionHelper className="layout-page-side-outer"> | |||
{({ top }) => ( | |||
@@ -186,8 +261,9 @@ export default class App extends React.PureComponent<Props, State> { | |||
<div className="layout-page-side-inner"> | |||
<div className="layout-page-filters"> | |||
<Sidebar | |||
measures={this.state.measures} | |||
selectedMetric={query.metric} | |||
hasOverview={hasOverview} | |||
measures={measures} | |||
selectedMetric={metric ? metric.key : query.metric} | |||
updateQuery={this.updateQuery} | |||
/> | |||
</div> | |||
@@ -196,37 +272,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
)} | |||
</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> | |||
); | |||
} |
@@ -28,7 +28,8 @@ import Tooltip from '../../../components/controls/Tooltip'; | |||
import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; | |||
import { getMeasureHistoryUrl } from '../../../helpers/urls'; | |||
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 { | |||
branchLike?: BranchLike; | |||
@@ -42,7 +43,9 @@ interface Props { | |||
export default function MeasureHeader(props: Props) { | |||
const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props; | |||
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 ( | |||
<div className="measure-details-header big-spacer-bottom"> | |||
<div className="measure-details-primary"> | |||
@@ -79,9 +82,10 @@ export default function MeasureHeader(props: Props) { | |||
)} | |||
</div> | |||
<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> | |||
{secondaryMeasure && |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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> | |||
); | |||
} |
@@ -21,6 +21,7 @@ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import App from '../App'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' }; | |||
@@ -48,21 +49,24 @@ const PROPS = { | |||
component: COMPONENT, | |||
currentUser: { isLoggedIn: false }, | |||
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(), | |||
metrics: METRICS, | |||
metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'], | |||
router: { push: jest.fn() } as any | |||
}; | |||
it('should render correctly', () => { | |||
it('should render correctly', async () => { | |||
const wrapper = shallow(<App {...PROPS} />); | |||
expect(wrapper.find('.spinner')).toHaveLength(1); | |||
wrapper.setState({ loading: false }); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should render a measure overview', () => { | |||
it('should render a measure overview', async () => { | |||
const wrapper = shallow( | |||
<App | |||
{...PROPS} | |||
@@ -70,6 +74,6 @@ it('should render a measure overview', () => { | |||
/> | |||
); | |||
expect(wrapper.find('.spinner')).toHaveLength(1); | |||
wrapper.setState({ loading: false }); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.find('MeasureOverviewContainer')).toHaveLength(1); | |||
}); |
@@ -81,10 +81,24 @@ it('should render correctly for leak', () => { | |||
).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( | |||
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(); | |||
}); | |||
@@ -128,7 +128,7 @@ exports[`should render correctly for leak 1`] = ` | |||
</div> | |||
`; | |||
exports[`should render with branch 1`] = ` | |||
exports[`should render with long living branch 1`] = ` | |||
<Link | |||
className="js-show-history spacer-left button button-small" | |||
onlyActiveOnIndex={false} | |||
@@ -137,7 +137,7 @@ exports[`should render with branch 1`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": "feature", | |||
"branch": "branch-6.7", | |||
"custom_metrics": "reliability_rating", | |||
"graph": "custom", | |||
"id": "foo", | |||
@@ -149,6 +149,41 @@ exports[`should render with branch 1`] = ` | |||
</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`] = ` | |||
<div | |||
className="measure-details-header big-spacer-bottom" |
@@ -41,6 +41,7 @@ import { MeasureEnhanced } from '../../../app/types'; | |||
interface Props { | |||
domain: { name: string; measures: MeasureEnhanced[] }; | |||
hasOverview: boolean; | |||
onChange: (metric: string) => void; | |||
onToggle: (property: string) => void; | |||
open: boolean; | |||
@@ -48,24 +49,28 @@ interface 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 = () => { | |||
this.props.onToggle(this.props.domain.name); | |||
}; | |||
hasFacetSelected = (domain: { name: string }, measures: MeasureEnhanced[], selected: string) => { | |||
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; | |||
}; | |||
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) => { | |||
@@ -115,7 +120,7 @@ export default class DomainFacet extends React.PureComponent<Props> { | |||
renderOverviewFacet = () => { | |||
const { domain, selected } = this.props; | |||
if (!hasBubbleChart(domain.name)) { | |||
if (!this.hasOverview(domain.name)) { | |||
return null; | |||
} | |||
return ( |
@@ -24,9 +24,10 @@ import { getDefaultView, groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } | |||
import { MeasureEnhanced } from '../../../app/types'; | |||
interface Props { | |||
hasOverview: boolean; | |||
measures: MeasureEnhanced[]; | |||
selectedMetric: string; | |||
updateQuery: (query: Query) => void; | |||
updateQuery: (query: Partial<Query>) => void; | |||
} | |||
interface State { | |||
@@ -73,16 +74,20 @@ export default class Sidebar extends React.PureComponent<Props, State> { | |||
this.props.updateQuery({ metric, ...this.resetSelection(metric) }); | |||
render() { | |||
const { hasOverview } = this.props; | |||
return ( | |||
<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 => ( | |||
<DomainFacet | |||
domain={domain} | |||
hasOverview={hasOverview} | |||
key={domain.name} | |||
onChange={this.changeMetric} | |||
onToggle={this.toggleFacet} |
@@ -51,10 +51,11 @@ const DOMAIN = { | |||
}; | |||
const PROPS = { | |||
domain: DOMAIN, | |||
hasOverview: true, | |||
onChange: () => {}, | |||
onToggle: () => {}, | |||
open: true, | |||
domain: DOMAIN, | |||
selected: 'foo' | |||
}; | |||
@@ -71,6 +72,16 @@ it('should render closed', () => { | |||
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', () => { | |||
const domain = { | |||
name: 'Reliability', | |||
@@ -82,17 +93,7 @@ it('should not display subtitles of new measures if there is none', () => { | |||
] | |||
}; | |||
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', () => { | |||
@@ -106,15 +107,5 @@ it('should not display subtitles of new measures if there is none, even on last | |||
] | |||
}; | |||
expect( | |||
shallow( | |||
<DomainFacet | |||
domain={domain} | |||
onChange={() => {}} | |||
onToggle={() => {}} | |||
open={true} | |||
selected={'foo'} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
expect(shallow(<DomainFacet {...PROPS} domain={domain} />)).toMatchSnapshot(); | |||
}); |
@@ -61,6 +61,7 @@ const MEASURES = [ | |||
]; | |||
const PROPS = { | |||
hasOverview: true, | |||
measures: MEASURES, | |||
selectedMetric: 'duplicated_lines_density', | |||
updateQuery: () => {} |
@@ -49,6 +49,7 @@ exports[`should display two facets 1`] = ` | |||
"name": "Coverage", | |||
} | |||
} | |||
hasOverview={true} | |||
key="Coverage" | |||
onChange={[Function]} | |||
onToggle={[Function]} | |||
@@ -80,6 +81,7 @@ exports[`should display two facets 1`] = ` | |||
"name": "Duplications", | |||
} | |||
} | |||
hasOverview={true} | |||
key="Duplications" | |||
onChange={[Function]} | |||
onToggle={[Function]} |
@@ -25,10 +25,13 @@ import { | |||
ComponentMeasure, | |||
ComponentMeasureEnhanced, | |||
Metric, | |||
MeasureEnhanced | |||
MeasureEnhanced, | |||
BranchLike | |||
} from '../../app/types'; | |||
import { enhanceMeasure } from '../../components/measure/utils'; | |||
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 DEFAULT_VIEW = 'list'; | |||
@@ -146,13 +149,33 @@ export function hasTreemap(metric: string, type: string): boolean { | |||
} | |||
export function hasBubbleChart(domainName: string): boolean { | |||
return bubbles[domainName] != null; | |||
return bubbles[domainName] !== undefined; | |||
} | |||
export function hasFacetStat(metric: string): boolean { | |||
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 }) { | |||
const conf = bubbles[domain]; | |||
return { | |||
@@ -182,7 +205,7 @@ const parseView = (metric: string, rawView?: string) => { | |||
}; | |||
export interface Query { | |||
metric?: string; | |||
metric: string; | |||
selected?: string; | |||
view: string; | |||
} |
@@ -2521,6 +2521,7 @@ component_measures.legend.size_x=Size: {0} | |||
component_measures.legend.worse_of_x_y=Worse of {0} and {1} | |||
component_measures.no_history=There is no historical data. | |||
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_navigate=to navigate | |||
component_measures.to_navigate_files=to next/previous file |