@@ -17,8 +17,10 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { FlagMessage } from 'design-system'; | |||
import { debounce, findLast, maxBy, minBy, sortBy } from 'lodash'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import GraphsHeader from '../../../components/activity-graph/GraphsHeader'; | |||
import GraphsHistory from '../../../components/activity-graph/GraphsHistory'; | |||
import GraphsZoom from '../../../components/activity-graph/GraphsZoom'; | |||
@@ -31,6 +33,9 @@ import { | |||
saveActivityGraph, | |||
splitSeriesInGraphs, | |||
} from '../../../components/activity-graph/utils'; | |||
import DocumentationLink from '../../../components/common/DocumentationLink'; | |||
import { CCT_SOFTWARE_QUALITY_METRICS } from '../../../helpers/constants'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { | |||
GraphType, | |||
MeasureHistory, | |||
@@ -198,6 +203,48 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St | |||
} | |||
}; | |||
renderQualitiesMetricInfoMessage = () => { | |||
const { measuresHistory } = this.props; | |||
const qualityMeasuresHistory = measuresHistory.find((history) => | |||
CCT_SOFTWARE_QUALITY_METRICS.includes(history.metric), | |||
); | |||
const indexOfFirstMeasureWithValue = qualityMeasuresHistory?.history.findIndex( | |||
(item) => item.value, | |||
); | |||
const hasGaps = | |||
indexOfFirstMeasureWithValue === -1 | |||
? false | |||
: qualityMeasuresHistory?.history | |||
.slice(indexOfFirstMeasureWithValue) | |||
.some((item) => item.value === undefined); | |||
if (hasGaps) { | |||
return ( | |||
<FlagMessage variant="info"> | |||
<FormattedMessage | |||
id="project_activity.graphs.data_table.data_gap" | |||
tagName="div" | |||
values={{ | |||
learn_more: ( | |||
<DocumentationLink | |||
className="sw-whitespace-nowrap" | |||
to="/user-guide/clean-code/code-analysis/" | |||
> | |||
{translate('learn_more')} | |||
</DocumentationLink> | |||
), | |||
}} | |||
/> | |||
</FlagMessage> | |||
); | |||
} | |||
return null; | |||
}; | |||
render() { | |||
const { analyses, leakPeriodDate, loading, measuresHistory, metrics, query } = this.props; | |||
const { graphEndDate, graphStartDate, series } = this.state; | |||
@@ -214,6 +261,7 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St | |||
selectedMetrics={query.customMetrics} | |||
onUpdateGraph={this.handleUpdateGraph} | |||
/> | |||
{this.renderQualitiesMetricInfoMessage()} | |||
<GraphsHistory | |||
analyses={analyses} | |||
graph={query.graph} |
@@ -86,12 +86,16 @@ beforeEach(() => { | |||
MetricKey.sqale_rating, | |||
MetricKey.security_hotspots_reviewed, | |||
MetricKey.security_review_rating, | |||
MetricKey.maintainability_issues, | |||
].map((metric) => | |||
mockMeasureHistory({ | |||
metric, | |||
history: projectActivityHandler | |||
.getAnalysesList() | |||
.map(({ date }) => mockHistoryItem({ value: '3', date: parseDate(date) })), | |||
history: projectActivityHandler.getAnalysesList().map(({ date }) => | |||
mockHistoryItem({ | |||
value: '3', | |||
date: parseDate(date), | |||
}), | |||
), | |||
}), | |||
), | |||
); | |||
@@ -241,6 +245,50 @@ describe('rendering', () => { | |||
).not.toBeInTheDocument(); | |||
}, | |||
); | |||
it('should render graph gap info message', async () => { | |||
timeMachineHandler.setMeasureHistory([ | |||
mockMeasureHistory({ | |||
metric: MetricKey.maintainability_issues, | |||
history: projectActivityHandler.getAnalysesList().map(({ date }, index) => | |||
mockHistoryItem({ | |||
// eslint-disable-next-line jest/no-conditional-in-test | |||
value: index === 0 ? '3' : undefined, | |||
date: parseDate(date), | |||
}), | |||
), | |||
}), | |||
]); | |||
const { ui } = getPageObject(); | |||
renderProjectActivityAppContainer( | |||
mockComponent({ | |||
breadcrumbs: [ | |||
{ key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Application }, | |||
], | |||
}), | |||
); | |||
await ui.changeGraphType(GraphType.custom); | |||
await ui.openMetricsDropdown(); | |||
await ui.toggleMetric(MetricKey.maintainability_issues); | |||
expect(ui.gapInfoMessage.get()).toBeInTheDocument(); | |||
}); | |||
it('should not render graph gap info message if no gaps', async () => { | |||
const { ui } = getPageObject(); | |||
renderProjectActivityAppContainer( | |||
mockComponent({ | |||
breadcrumbs: [ | |||
{ key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Application }, | |||
], | |||
}), | |||
); | |||
await ui.changeGraphType(GraphType.custom); | |||
await ui.openMetricsDropdown(); | |||
await ui.toggleMetric(MetricKey.maintainability_issues); | |||
expect(ui.gapInfoMessage.query()).not.toBeInTheDocument(); | |||
}); | |||
}); | |||
describe('CRUD', () => { | |||
@@ -512,6 +560,7 @@ function getPageObject() { | |||
// Graphs. | |||
graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }), | |||
noDataText: byText('project_activity.graphs.custom.no_history'), | |||
gapInfoMessage: byText('project_activity.graphs.data_table.data_gap', { exact: false }), | |||
// Add metrics. | |||
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }), | |||
@@ -697,6 +746,7 @@ function renderProjectActivityAppContainer( | |||
{ | |||
metrics: keyBy( | |||
[ | |||
mockMetric({ key: MetricKey.maintainability_issues, type: MetricType.Data }), | |||
mockMetric({ key: MetricKey.bugs, type: MetricType.Integer }), | |||
mockMetric({ key: MetricKey.code_smells, type: MetricType.Integer }), | |||
mockMetric({ key: MetricKey.security_hotspots_reviewed }), |
@@ -1950,6 +1950,7 @@ project_activity.graphs.data_table.no_data_warning=There is no data for the sele | |||
project_activity.graphs.data_table.no_data_warning_check_dates_x=There is no data for the selected date range (everything after {start}). Try modifying the date filters on the main page. | |||
project_activity.graphs.data_table.no_data_warning_check_dates_y=There is no data for the selected date range (everything before {end}). Try modifying the date filters on the main page. | |||
project_activity.graphs.data_table.no_data_warning_check_dates_x_y=There is no data for the selected date range ({start} to {end}). Try modifying the date filters on the main page. | |||
project_activity.graphs.data_table.data_gap=The chart history for issues related to software qualities may contain gaps while information is not available for one or more projects. {learn_more} | |||
project_activity.custom_metric.covered_lines=Covered Lines | |||