@@ -97,6 +97,8 @@ export class App extends React.PureComponent<Props> { | |||
<OverviewApp | |||
branchLike={branchLike} | |||
component={component} | |||
isInProgress={this.props.isInProgress} | |||
isPending={this.props.isPending} | |||
onComponentChange={this.props.onComponentChange} | |||
/> | |||
)} |
@@ -20,8 +20,10 @@ | |||
import { uniq } from 'lodash'; | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import { parseDate } from 'sonar-ui-common/helpers/dates'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; | |||
import { getMeasuresAndMeta } from '../../../api/measures'; | |||
import { getAllTimeMachineData } from '../../../api/time-machine'; | |||
import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; | |||
@@ -37,6 +39,7 @@ import { getLeakPeriod } from '../../../helpers/periods'; | |||
import { fetchMetrics } from '../../../store/rootActions'; | |||
import { getMetrics, Store } from '../../../store/rootReducer'; | |||
import { BranchLike } from '../../../types/branch-like'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { | |||
DEFAULT_GRAPH, | |||
getDisplayedHistoryMetrics, | |||
@@ -56,6 +59,8 @@ import { HISTORY_METRICS_LIST, METRICS } from '../utils'; | |||
interface Props { | |||
branchLike?: BranchLike; | |||
component: T.Component; | |||
isInProgress?: boolean; | |||
isPending?: boolean; | |||
fetchMetrics: () => void; | |||
onComponentChange: (changes: {}) => void; | |||
metrics: T.Dict<T.Metric>; | |||
@@ -101,9 +106,10 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
}; | |||
isEmpty = () => { | |||
const { measures } = this.state; | |||
return ( | |||
this.state.measures === undefined || | |||
this.state.measures.find(measure => measure.metric.key === 'ncloc') === undefined | |||
measures === undefined || | |||
measures.find(measure => ['lines', 'new_lines'].includes(measure.metric.key)) === undefined | |||
); | |||
}; | |||
@@ -164,7 +170,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
renderEmpty = () => { | |||
const { branchLike, component } = this.props; | |||
const isApp = component.qualifier === 'APP'; | |||
const isApp = component.qualifier === ComponentQualifier.Application; | |||
/* eslint-disable no-lonely-if */ | |||
// - Is App | |||
@@ -217,11 +223,34 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
/* eslint-enable no-lonely-if */ | |||
return ( | |||
<div className="overview-main page-main"> | |||
{this.renderNewAnalysisRequired()} | |||
<h3>{title}</h3> | |||
</div> | |||
); | |||
}; | |||
renderNewAnalysisRequired = () => { | |||
const { component, isInProgress, isPending } = this.props; | |||
const { measures, periods } = this.state; | |||
const leakPeriod = getLeakPeriod(periods); | |||
if ( | |||
!isInProgress && | |||
!isPending && | |||
component.qualifier !== ComponentQualifier.Application && | |||
measures.some(m => isDiffMetric(m.metric.key)) && | |||
leakPeriod === undefined | |||
) { | |||
return ( | |||
<Alert className="big-spacer-bottom" display="inline" variant="warning"> | |||
{translate('overview.project.branch_needs_new_analysis')} | |||
</Alert> | |||
); | |||
} else { | |||
return null; | |||
} | |||
}; | |||
renderLoading = () => { | |||
return ( | |||
<div className="text-center"> | |||
@@ -234,7 +263,9 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
const { branchLike, component } = this.props; | |||
const { periods, measures, history, historyStartDate } = this.state; | |||
const leakPeriod = | |||
component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods); | |||
component.qualifier === ComponentQualifier.Application | |||
? this.getApplicationLeakPeriod() | |||
: getLeakPeriod(periods); | |||
const domainProps = { | |||
branchLike, | |||
component, | |||
@@ -250,7 +281,9 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
return ( | |||
<div className="overview-main page-main"> | |||
{component.qualifier === 'APP' ? ( | |||
{this.renderNewAnalysisRequired()} | |||
{component.qualifier === ComponentQualifier.Application ? ( | |||
<ApplicationQualityGate | |||
branch={isBranch(branchLike) && !isMainBranch(branchLike) ? branchLike : undefined} | |||
component={component} |
@@ -31,10 +31,10 @@ jest.mock('../../../../api/measures', () => { | |||
return { | |||
getMeasuresAndMeta: jest.fn().mockResolvedValue({ | |||
component: { | |||
measures: [mockMeasure({ metric: 'ncloc' }), mockMeasure({ metric: 'coverage' })], | |||
measures: [mockMeasure({ metric: 'lines' }), mockMeasure({ metric: 'coverage' })], | |||
name: 'foo' | |||
}, | |||
metrics: [mockMetric({ key: 'ncloc' }), mockMetric()] | |||
metrics: [mockMetric({ key: 'lines' }), mockMetric()] | |||
}) | |||
}; | |||
}); | |||
@@ -50,7 +50,7 @@ jest.mock('../../../../api/time-machine', () => ({ | |||
{ metric: 'vulnerabilities', history: [{ date: '2019-01-05', value: '0' }] }, | |||
{ metric: 'sqale_index', history: [{ date: '2019-01-01', value: '1.0' }] }, | |||
{ metric: 'duplicated_lines_density', history: [{ date: '2019-01-02', value: '1.0' }] }, | |||
{ metric: 'ncloc', history: [{ date: '2019-01-03', value: '10000' }] }, | |||
{ metric: 'lines', history: [{ date: '2019-01-03', value: '10000' }] }, | |||
{ metric: 'coverage', history: [{ date: '2019-01-04', value: '95.5' }] } | |||
] | |||
}) | |||
@@ -137,6 +137,25 @@ it('should show the correct message if the project has no lines of code', async | |||
expect(wrapper.find('h3').text()).toBe('overview.project.no_lines_of_code'); | |||
}); | |||
it('should show a warning if the project has new measures, but no period info', async () => { | |||
(getMeasuresAndMeta as jest.Mock).mockResolvedValue({ | |||
component: { | |||
measures: [mockMeasure({ metric: 'bugs' }), mockMeasure({ metric: 'new_bugs' })], | |||
name: 'foo' | |||
}, | |||
metrics: [mockMetric({ key: 'bugs' }), mockMetric({ key: 'new_bugs' })] | |||
}); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect( | |||
wrapper | |||
.find('Alert') | |||
.dive() | |||
.text() | |||
).toContain('overview.project.branch_needs_new_analysis'); | |||
}); | |||
function getMockHelpers() { | |||
// We use this little "force-requiring" instead of an import statement in | |||
// order to prevent a hoisting race condition while mocking. If we want to use |
@@ -61,7 +61,7 @@ exports[`should render correctly 2`] = ` | |||
"bestValue": true, | |||
"metric": Object { | |||
"id": "coverage", | |||
"key": "ncloc", | |||
"key": "lines", | |||
"name": "Coverage", | |||
"type": "PERCENT", | |||
}, | |||
@@ -149,7 +149,7 @@ exports[`should render correctly 2`] = ` | |||
"value": "1.0", | |||
}, | |||
], | |||
"ncloc": Array [ | |||
"lines": Array [ | |||
Object { | |||
"date": "2019-01-03", | |||
"value": "10000", | |||
@@ -176,7 +176,7 @@ exports[`should render correctly 2`] = ` | |||
"bestValue": true, | |||
"metric": Object { | |||
"id": "coverage", | |||
"key": "ncloc", | |||
"key": "lines", | |||
"name": "Coverage", | |||
"type": "PERCENT", | |||
}, | |||
@@ -261,7 +261,7 @@ exports[`should render correctly 2`] = ` | |||
"value": "1.0", | |||
}, | |||
], | |||
"ncloc": Array [ | |||
"lines": Array [ | |||
Object { | |||
"date": "2019-01-03", | |||
"value": "10000", | |||
@@ -288,7 +288,7 @@ exports[`should render correctly 2`] = ` | |||
"bestValue": true, | |||
"metric": Object { | |||
"id": "coverage", | |||
"key": "ncloc", | |||
"key": "lines", | |||
"name": "Coverage", | |||
"type": "PERCENT", | |||
}, | |||
@@ -373,7 +373,7 @@ exports[`should render correctly 2`] = ` | |||
"value": "1.0", | |||
}, | |||
], | |||
"ncloc": Array [ | |||
"lines": Array [ | |||
Object { | |||
"date": "2019-01-03", | |||
"value": "10000", | |||
@@ -400,7 +400,7 @@ exports[`should render correctly 2`] = ` | |||
"bestValue": true, | |||
"metric": Object { | |||
"id": "coverage", | |||
"key": "ncloc", | |||
"key": "lines", | |||
"name": "Coverage", | |||
"type": "PERCENT", | |||
}, | |||
@@ -485,7 +485,7 @@ exports[`should render correctly 2`] = ` | |||
"value": "1.0", | |||
}, | |||
], | |||
"ncloc": Array [ | |||
"lines": Array [ | |||
Object { | |||
"date": "2019-01-03", | |||
"value": "10000", | |||
@@ -512,7 +512,7 @@ exports[`should render correctly 2`] = ` | |||
"bestValue": true, | |||
"metric": Object { | |||
"id": "coverage", | |||
"key": "ncloc", | |||
"key": "lines", | |||
"name": "Coverage", | |||
"type": "PERCENT", | |||
}, | |||
@@ -597,7 +597,7 @@ exports[`should render correctly 2`] = ` | |||
"value": "1.0", | |||
}, | |||
], | |||
"ncloc": Array [ | |||
"lines": Array [ | |||
Object { | |||
"date": "2019-01-03", | |||
"value": "10000", | |||
@@ -624,7 +624,7 @@ exports[`should render correctly 2`] = ` | |||
"bestValue": true, | |||
"metric": Object { | |||
"id": "coverage", | |||
"key": "ncloc", | |||
"key": "lines", | |||
"name": "Coverage", | |||
"type": "PERCENT", | |||
}, | |||
@@ -714,7 +714,7 @@ exports[`should render correctly 2`] = ` | |||
"value": "1.0", | |||
}, | |||
], | |||
"ncloc": Array [ | |||
"lines": Array [ | |||
Object { | |||
"date": "2019-01-03", | |||
"value": "10000", | |||
@@ -740,7 +740,7 @@ exports[`should render correctly 2`] = ` | |||
"bestValue": true, | |||
"metric": Object { | |||
"id": "coverage", | |||
"key": "ncloc", | |||
"key": "lines", | |||
"name": "Coverage", | |||
"type": "PERCENT", | |||
}, |
@@ -54,18 +54,20 @@ export class Bugs extends React.PureComponent<ComposedProps> { | |||
renderLeak() { | |||
const { branchLike, component, leakPeriod } = this.props; | |||
if (!leakPeriod) { | |||
if (!this.props.hasDiffMetrics()) { | |||
return null; | |||
} | |||
return ( | |||
<div className="overview-domain-leak"> | |||
{component.qualifier === 'APP' ? ( | |||
{component.qualifier === 'APP' && ( | |||
<ApplicationLeakPeriodLegend | |||
branch={isBranch(branchLike) && !isMainBranch(branchLike) ? branchLike : undefined} | |||
component={component} | |||
/> | |||
) : ( | |||
)} | |||
{component.qualifier !== 'APP' && leakPeriod !== undefined && ( | |||
<LeakPeriodLegend period={leakPeriod} /> | |||
)} | |||
@@ -47,8 +47,7 @@ export class CodeSmells extends React.PureComponent<ComposedProps> { | |||
} | |||
renderLeak() { | |||
const { leakPeriod } = this.props; | |||
if (!leakPeriod) { | |||
if (!this.props.hasDiffMetrics()) { | |||
return null; | |||
} | |||
@@ -26,7 +26,6 @@ import { | |||
import DocTooltip from '../../../components/docs/DocTooltip'; | |||
import DrilldownLink from '../../../components/shared/DrilldownLink'; | |||
import CoverageRating from '../../../components/ui/CoverageRating'; | |||
import { getPeriodValue } from '../../../helpers/measures'; | |||
import { getMetricName, getThreshold } from '../utils'; | |||
import enhance, { ComposedProps } from './enhance'; | |||
@@ -89,14 +88,10 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
} | |||
renderNewCoverage() { | |||
const { branchLike, component, leakPeriod, measures } = this.props; | |||
if (!leakPeriod) { | |||
return null; | |||
} | |||
const { branchLike, component, measures } = this.props; | |||
const newCoverageMeasure = measures.find(measure => measure.metric.key === 'new_coverage'); | |||
const newCoverageValue = | |||
newCoverageMeasure && getPeriodValue(newCoverageMeasure, leakPeriod.index); | |||
const newCoverageValue = newCoverageMeasure && this.props.getValue(newCoverageMeasure); | |||
const formattedValue = | |||
newCoverageMeasure && newCoverageValue !== undefined ? ( | |||
<div> | |||
@@ -119,8 +114,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
); | |||
const newLinesToCover = measures.find(measure => measure.metric.key === 'new_lines_to_cover'); | |||
const newLinesToCoverValue = | |||
newLinesToCover && getPeriodValue(newLinesToCover, leakPeriod.index); | |||
const newLinesToCoverValue = newLinesToCover && this.props.getValue(newLinesToCover); | |||
const label = | |||
newLinesToCover && newLinesToCoverValue !== undefined && Number(newLinesToCoverValue) > 0 ? ( | |||
<div className="overview-domain-measure-label"> | |||
@@ -165,8 +159,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
} | |||
renderLeak() { | |||
const { leakPeriod } = this.props; | |||
if (!leakPeriod) { | |||
if (!this.props.hasDiffMetrics()) { | |||
return null; | |||
} | |||
return ( |
@@ -26,7 +26,6 @@ import { | |||
} from 'sonar-ui-common/helpers/measures'; | |||
import DocTooltip from '../../../components/docs/DocTooltip'; | |||
import DrilldownLink from '../../../components/shared/DrilldownLink'; | |||
import { getPeriodValue } from '../../../helpers/measures'; | |||
import { getMetricName, getThreshold } from '../utils'; | |||
import enhance, { ComposedProps } from './enhance'; | |||
@@ -88,15 +87,12 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
} | |||
renderNewDuplications() { | |||
const { branchLike, component, measures, leakPeriod } = this.props; | |||
if (!leakPeriod) { | |||
return null; | |||
} | |||
const { branchLike, component, measures } = this.props; | |||
const newDuplicationsMeasure = measures.find( | |||
measure => measure.metric.key === 'new_duplicated_lines_density' | |||
); | |||
const newDuplicationsValue = | |||
newDuplicationsMeasure && getPeriodValue(newDuplicationsMeasure, leakPeriod.index); | |||
newDuplicationsMeasure && this.props.getValue(newDuplicationsMeasure); | |||
const formattedValue = | |||
newDuplicationsMeasure && newDuplicationsValue ? ( | |||
<div> | |||
@@ -119,7 +115,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
); | |||
const newLinesMeasure = measures.find(measure => measure.metric.key === 'new_lines'); | |||
const newLinesValue = newLinesMeasure && getPeriodValue(newLinesMeasure, leakPeriod.index); | |||
const newLinesValue = newLinesMeasure && this.props.getValue(newLinesMeasure); | |||
const label = | |||
newLinesMeasure && newLinesValue !== undefined && Number(newLinesValue) > 0 ? ( | |||
<div className="overview-domain-measure-label"> | |||
@@ -162,8 +158,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
} | |||
renderLeak() { | |||
const { leakPeriod } = this.props; | |||
if (leakPeriod == null) { | |||
if (!this.props.hasDiffMetrics()) { | |||
return null; | |||
} | |||
return ( |
@@ -34,8 +34,7 @@ export class VulnerabiltiesAndHotspots extends React.PureComponent<ComposedProps | |||
} | |||
renderLeak() { | |||
const { leakPeriod } = this.props; | |||
if (!leakPeriod) { | |||
if (!this.props.hasDiffMetrics()) { | |||
return null; | |||
} | |||
@@ -25,14 +25,10 @@ import Rating from 'sonar-ui-common/components/ui/Rating'; | |||
import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | |||
import { getWrappedDisplayName } from '../../../components/hoc/utils'; | |||
import { getLeakValue } from '../../../components/measure/utils'; | |||
import DrilldownLink from '../../../components/shared/DrilldownLink'; | |||
import { getBranchLikeQuery } from '../../../helpers/branch-like'; | |||
import { | |||
getPeriodValue, | |||
getRatingTooltip, | |||
getShortType, | |||
isDiffMetric | |||
} from '../../../helpers/measures'; | |||
import { getRatingTooltip, getShortType, isDiffMetric } from '../../../helpers/measures'; | |||
import { getPeriodDate } from '../../../helpers/periods'; | |||
import { | |||
getComponentDrilldownUrl, | |||
@@ -55,6 +51,7 @@ export interface EnhanceProps { | |||
export interface ComposedProps extends EnhanceProps { | |||
getValue: (measure: T.MeasureEnhanced) => string | undefined; | |||
hasDiffMetrics: () => boolean; | |||
renderHeader: (domain: string, label?: string) => React.ReactNode; | |||
renderMeasure: (metricKey: string, tooltip?: React.ReactNode) => React.ReactNode; | |||
renderRating: (metricKey: string) => React.ReactNode; | |||
@@ -68,13 +65,15 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
static displayName = getWrappedDisplayName(ComposedComponent, 'enhance'); | |||
getValue = (measure: T.MeasureEnhanced) => { | |||
const { leakPeriod } = this.props; | |||
if (!measure) { | |||
return '0'; | |||
} | |||
return isDiffMetric(measure.metric.key) | |||
? getPeriodValue(measure, leakPeriod ? leakPeriod.index : 0) | |||
: measure.value; | |||
return isDiffMetric(measure.metric.key) ? getLeakValue(measure) : measure.value; | |||
}; | |||
hasDiffMetrics = () => { | |||
const { measures } = this.props; | |||
return measures.some(m => isDiffMetric(m.metric.key)); | |||
}; | |||
renderHeader = (domain: string, label?: string) => { | |||
@@ -202,6 +201,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
<ComposedComponent | |||
{...this.props} | |||
getValue={this.getValue} | |||
hasDiffMetrics={this.hasDiffMetrics} | |||
renderHeader={this.renderHeader} | |||
renderHistoryLink={this.renderHistoryLink} | |||
renderIssues={this.renderIssues} |
@@ -65,6 +65,7 @@ export const METRICS = [ | |||
// size | |||
'ncloc', | |||
'lines', | |||
'ncloc_language_distribution', | |||
'projects', | |||
'new_lines' |
@@ -35,7 +35,7 @@ export function enhanceMeasure(measure: T.Measure, metrics: T.Dict<T.Metric>): T | |||
}; | |||
} | |||
export function getLeakValue(measure: T.Measure | undefined): string | undefined { | |||
export function getLeakValue(measure: T.MeasureIntern | undefined): string | undefined { | |||
if (!measure || !measure.periods) { | |||
return undefined; | |||
} |
@@ -2588,6 +2588,7 @@ overview.project.branch_X_no_lines_of_code=The "{0}" branch has no lines of code | |||
overview.project.branch_X_empty=The "{0}" branch of this project is empty. | |||
overview.project.main_branch_no_lines_of_code=The main branch has no lines of code. | |||
overview.project.main_branch_empty=The main branch of this project is empty. | |||
overview.project.branch_needs_new_analysis=The branch data is incomplete. Run a new analysis to update it. | |||
overview.metric.code_smells=Code Smells | |||
overview.metric.new_code_smells=New Code Smells |