@@ -19,42 +19,48 @@ | |||
*/ | |||
import { getJSON, RequestData } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { Measure, MeasurePeriod } from '../helpers/measures'; | |||
import { Metric } from '../app/types'; | |||
import { Period } from '../helpers/periods'; | |||
export function getMeasures( | |||
componentKey: string, | |||
metrics: string[], | |||
branch?: string | |||
): Promise<Array<{ metric: string; value?: string }>> { | |||
): Promise<{ metric: string; value?: string }[]> { | |||
const url = '/api/measures/component'; | |||
const data = { componentKey, metricKeys: metrics.join(','), branch }; | |||
return getJSON(url, data).then(r => r.component.measures, throwGlobalError); | |||
} | |||
interface MeasureComponent { | |||
key: string; | |||
description?: string; | |||
measures: Measure[]; | |||
name: string; | |||
qualifier: string; | |||
} | |||
export function getMeasuresAndMeta( | |||
componentKey: string, | |||
metrics: string[], | |||
additional: RequestData = {} | |||
): Promise<any> { | |||
): Promise<{ component: MeasureComponent; metrics?: Metric[]; periods?: Period[] }> { | |||
const data = { ...additional, componentKey, metricKeys: metrics.join(',') }; | |||
return getJSON('/api/measures/component', data); | |||
} | |||
export interface Period { | |||
index: number; | |||
value: string; | |||
} | |||
export interface Measure { | |||
interface MeasuresForProjects { | |||
component: string; | |||
metric: string; | |||
periods?: Period[]; | |||
periods?: MeasurePeriod[]; | |||
value?: string; | |||
} | |||
export function getMeasuresForProjects( | |||
projectKeys: string[], | |||
metricKeys: string[] | |||
): Promise<Measure[]> { | |||
): Promise<MeasuresForProjects[]> { | |||
return getJSON('/api/measures/search', { | |||
projectKeys: projectKeys.join(), | |||
metricKeys: metricKeys.join() |
@@ -18,19 +18,29 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getJSON } from '../helpers/request'; | |||
import { Paging } from '../app/types'; | |||
export interface HistoryItem { | |||
date: Date; | |||
value: string; | |||
} | |||
export interface History { | |||
[metric: string]: HistoryItem[]; | |||
} | |||
interface TimeMachineResponse { | |||
measures: Array<{ | |||
measures: { | |||
metric: string; | |||
history: Array<{ date: string; value: string }>; | |||
}>; | |||
paging: { pageIndex: number; pageSize: number; total: number }; | |||
history: HistoryItem[]; | |||
}[]; | |||
paging: Paging; | |||
} | |||
export function getTimeMachineData( | |||
component: string, | |||
metrics: string[], | |||
other?: { p?: number; ps?: number; from?: string; to?: string } | |||
other?: { branch?: string; p?: number; ps?: number; from?: string; to?: string } | |||
): Promise<TimeMachineResponse> { | |||
return getJSON('/api/measures/search_history', { | |||
component, | |||
@@ -43,7 +53,7 @@ export function getTimeMachineData( | |||
export function getAllTimeMachineData( | |||
component: string, | |||
metrics: Array<string>, | |||
other?: { p?: number; from?: string; to?: string }, | |||
other?: { branch?: string; p?: number; from?: string; to?: string }, | |||
prev?: TimeMachineResponse | |||
): Promise<TimeMachineResponse> { | |||
return getTimeMachineData(component, metrics, { ...other, ps: 1000 }).then(r => { |
@@ -105,7 +105,7 @@ export interface Metric { | |||
domain?: string; | |||
hidden?: boolean; | |||
key: string; | |||
name: string; | |||
name?: string; | |||
qualitative?: boolean; | |||
type: string; | |||
} |
@@ -17,16 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
interface Measure { | |||
metric: string; | |||
value: string; | |||
periods?: Period[]; | |||
} | |||
interface Period { | |||
index: number; | |||
value: string; | |||
} | |||
import { Measure } from '../../helpers/measures'; | |||
export interface Component extends Breadcrumb { | |||
measures?: Measure[]; |
@@ -1,104 +0,0 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import OverviewApp from './OverviewApp'; | |||
import EmptyOverview from './EmptyOverview'; | |||
import { getBranchName, isShortLivingBranch } from '../../../helpers/branches'; | |||
import { getProjectBranchUrl, getCodeUrl } from '../../../helpers/urls'; | |||
/*:: | |||
type Props = { | |||
branch?: { name: string }, | |||
component: { | |||
analysisDate?: string, | |||
breadcrumbs: Array<{ key: string }>, | |||
id: string, | |||
key: string, | |||
qualifier: string, | |||
tags: Array<string>, | |||
organization?: string | |||
}, | |||
isInProgress?: bool, | |||
isPending?: bool, | |||
onComponentChange: {} => void, | |||
router: Object | |||
}; | |||
*/ | |||
export default class App extends React.PureComponent { | |||
/*:: props: Props; */ | |||
/*:: state: Object; */ | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
componentDidMount() { | |||
const { branch, component } = this.props; | |||
if (this.isPortfolio()) { | |||
this.context.router.replace({ | |||
pathname: '/portfolio', | |||
query: { id: component.key } | |||
}); | |||
} else if (this.isFile()) { | |||
this.context.router.replace( | |||
getCodeUrl(component.breadcrumbs[0].key, getBranchName(branch), component.key) | |||
); | |||
} else if (isShortLivingBranch(branch)) { | |||
this.context.router.replace(getProjectBranchUrl(component.key, branch)); | |||
} | |||
} | |||
isPortfolio() { | |||
return ['VW', 'SVW'].includes(this.props.component.qualifier); | |||
} | |||
isFile() { | |||
return ['FIL', 'UTS'].includes(this.props.component.qualifier); | |||
} | |||
render() { | |||
const { branch, component } = this.props; | |||
if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branch)) { | |||
return null; | |||
} | |||
if (!component.analysisDate) { | |||
return ( | |||
<EmptyOverview | |||
component={component.key} | |||
showWarning={!this.props.isPending && !this.props.isInProgress} | |||
/> | |||
); | |||
} | |||
return ( | |||
<OverviewApp | |||
branch={branch} | |||
component={component} | |||
onComponentChange={this.props.onComponentChange} | |||
/> | |||
); | |||
} | |||
} |
@@ -1,51 +0,0 @@ | |||
/* | |||
* 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 React from 'react'; | |||
import { mount, shallow } from 'enzyme'; | |||
import App from '../App'; | |||
import OverviewApp from '../OverviewApp'; | |||
import EmptyOverview from '../EmptyOverview'; | |||
it('should render OverviewApp', () => { | |||
const component = { key: 'foo', analysisDate: '2016-01-01' }; | |||
const output = shallow(<App component={component} />); | |||
expect(output.type()).toBe(OverviewApp); | |||
}); | |||
it('should render EmptyOverview', () => { | |||
const component = { key: 'foo' }; | |||
const output = shallow(<App component={component} />); | |||
expect(output.type()).toBe(EmptyOverview); | |||
}); | |||
it('redirects on Code page for files', () => { | |||
const branch = { isMain: false, name: 'b' }; | |||
const component = { | |||
breadcrumbs: [{ key: 'project' }, { key: 'foo' }], | |||
key: 'foo', | |||
qualifier: 'FIL' | |||
}; | |||
const replace = jest.fn(); | |||
mount(<App branch={branch} component={component} />, { context: { router: { replace } } }); | |||
expect(replace).toBeCalledWith({ | |||
pathname: '/code', | |||
query: { branch: 'b', id: 'project', selected: 'foo' } | |||
}); | |||
}); |
@@ -30,8 +30,7 @@ interface Props { | |||
} | |||
export default function Summary({ component, measures }: Props) { | |||
const projects = measures['projects']; | |||
const ncloc = measures['ncloc']; | |||
const { projects, ncloc } = measures; | |||
const nclocDistribution = measures['ncloc_language_distribution']; | |||
return ( |
@@ -21,8 +21,8 @@ import * as React from 'react'; | |||
import Rating from '../ui/Rating'; | |||
import Level from '../ui/Level'; | |||
import Tooltips from '../controls/Tooltip'; | |||
import { formatMeasure, isDiffMetric } from '../../helpers/measures'; | |||
import { formatLeak, getRatingTooltip, MeasureEnhanced } from './utils'; | |||
import { formatMeasure, isDiffMetric, MeasureEnhanced } from '../../helpers/measures'; | |||
import { formatLeak, getRatingTooltip } from './utils'; | |||
interface Props { | |||
className?: string; | |||
@@ -35,7 +35,7 @@ export default function Measure({ className, decimals, measure }: Props) { | |||
return <span>{'–'}</span>; | |||
} | |||
const metric = measure.metric; | |||
const { metric } = measure; | |||
const value = isDiffMetric(metric.key) ? measure.leak : measure.value; | |||
if (value === undefined) { |
@@ -21,33 +21,20 @@ import { | |||
formatMeasure, | |||
formatMeasureVariation, | |||
getRatingTooltip as nextGetRatingTooltip, | |||
isDiffMetric | |||
isDiffMetric, | |||
Measure, | |||
MeasureEnhanced | |||
} from '../../helpers/measures'; | |||
import { Metric } from '../../app/types'; | |||
const KNOWN_RATINGS = ['sqale_rating', 'reliability_rating', 'security_rating']; | |||
export interface MeasureIntern { | |||
value?: string; | |||
periods?: Array<{ index: number; value: string }>; | |||
} | |||
export interface Measure extends MeasureIntern { | |||
metric: string; | |||
} | |||
export interface MeasureEnhanced extends MeasureIntern { | |||
metric: { key: string; type: string }; | |||
leak?: string | undefined | undefined; | |||
} | |||
export function enhanceMeasure( | |||
measure: Measure, | |||
metrics: { [key: string]: Metric } | |||
): MeasureEnhanced { | |||
return { | |||
value: measure.value, | |||
periods: measure.periods, | |||
...measure, | |||
metric: metrics[measure.metric], | |||
leak: getLeakValue(measure) | |||
}; |
@@ -153,18 +153,21 @@ export function getLocalizedDashboardName(baseName: string) { | |||
} | |||
export function getLocalizedMetricName( | |||
metric: { key: string; name: string }, | |||
metric: { key: string; name?: string }, | |||
short?: boolean | |||
): string { | |||
const bundleKey = `metric.${metric.key}.${short ? 'short_name' : 'name'}`; | |||
const fromBundle = translate(bundleKey); | |||
if (fromBundle === bundleKey) { | |||
return short ? getLocalizedMetricName(metric) : metric.name; | |||
if (short) { | |||
return getLocalizedMetricName(metric); | |||
} | |||
return metric.name || metric.key; | |||
} | |||
return fromBundle; | |||
} | |||
export function getLocalizedCategoryMetricName(metric: { key: string; name: string }) { | |||
export function getLocalizedCategoryMetricName(metric: { key: string; name?: string }) { | |||
const bundleKey = `metric.${metric.key}.extra_short_name`; | |||
const fromBundle = translate(bundleKey); | |||
return fromBundle === bundleKey ? getLocalizedMetricName(metric, true) : fromBundle; |
@@ -18,20 +18,27 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { translate, translateWithParameters, getCurrentLocale } from './l10n'; | |||
import { Metric } from '../app/types'; | |||
const HOURS_IN_DAY = 8; | |||
interface Measure { | |||
metric: string; | |||
periods?: any[]; | |||
export interface MeasurePeriod { | |||
index: number; | |||
value: string; | |||
} | |||
interface EnhancedMeasure { | |||
metric: Metric; | |||
export interface MeasureIntern { | |||
value?: string; | |||
periods?: MeasurePeriod[]; | |||
} | |||
export interface Measure extends MeasureIntern { | |||
metric: string; | |||
} | |||
interface Metric { | |||
key: string; | |||
export interface MeasureEnhanced extends MeasureIntern { | |||
metric: Metric; | |||
leak?: string; | |||
} | |||
interface Formatter { | |||
@@ -76,7 +83,7 @@ export function getShortType(type: string): string { | |||
export function enhanceMeasuresWithMetrics( | |||
measures: Measure[], | |||
metrics: Metric[] | |||
): EnhancedMeasure[] { | |||
): MeasureEnhanced[] { | |||
return measures.map(measure => { | |||
const metric = metrics.find(metric => metric.key === measure.metric) as Metric; | |||
return { ...measure, metric }; |
@@ -20,7 +20,7 @@ | |||
import { translate, translateWithParameters } from './l10n'; | |||
import { parseDate } from './dates'; | |||
interface Period { | |||
export interface Period { | |||
date: string; | |||
index: number; | |||
mode: string; |