* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * 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 { debounce, findLast, maxBy, minBy, sortBy } from 'lodash'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | |||||
import GraphsHeader from '../../../components/activity-graph/GraphsHeader'; | import GraphsHeader from '../../../components/activity-graph/GraphsHeader'; | ||||
import GraphsHistory from '../../../components/activity-graph/GraphsHistory'; | import GraphsHistory from '../../../components/activity-graph/GraphsHistory'; | ||||
import GraphsZoom from '../../../components/activity-graph/GraphsZoom'; | import GraphsZoom from '../../../components/activity-graph/GraphsZoom'; | ||||
saveActivityGraph, | saveActivityGraph, | ||||
splitSeriesInGraphs, | splitSeriesInGraphs, | ||||
} from '../../../components/activity-graph/utils'; | } 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 { | import { | ||||
GraphType, | GraphType, | ||||
MeasureHistory, | MeasureHistory, | ||||
} | } | ||||
}; | }; | ||||
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() { | render() { | ||||
const { analyses, leakPeriodDate, loading, measuresHistory, metrics, query } = this.props; | const { analyses, leakPeriodDate, loading, measuresHistory, metrics, query } = this.props; | ||||
const { graphEndDate, graphStartDate, series } = this.state; | const { graphEndDate, graphStartDate, series } = this.state; | ||||
selectedMetrics={query.customMetrics} | selectedMetrics={query.customMetrics} | ||||
onUpdateGraph={this.handleUpdateGraph} | onUpdateGraph={this.handleUpdateGraph} | ||||
/> | /> | ||||
{this.renderQualitiesMetricInfoMessage()} | |||||
<GraphsHistory | <GraphsHistory | ||||
analyses={analyses} | analyses={analyses} | ||||
graph={query.graph} | graph={query.graph} |
MetricKey.sqale_rating, | MetricKey.sqale_rating, | ||||
MetricKey.security_hotspots_reviewed, | MetricKey.security_hotspots_reviewed, | ||||
MetricKey.security_review_rating, | MetricKey.security_review_rating, | ||||
MetricKey.maintainability_issues, | |||||
].map((metric) => | ].map((metric) => | ||||
mockMeasureHistory({ | mockMeasureHistory({ | ||||
metric, | metric, | ||||
history: projectActivityHandler | |||||
.getAnalysesList() | |||||
.map(({ date }) => mockHistoryItem({ value: '3', date: parseDate(date) })), | |||||
history: projectActivityHandler.getAnalysesList().map(({ date }) => | |||||
mockHistoryItem({ | |||||
value: '3', | |||||
date: parseDate(date), | |||||
}), | |||||
), | |||||
}), | }), | ||||
), | ), | ||||
); | ); | ||||
).not.toBeInTheDocument(); | ).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', () => { | describe('CRUD', () => { | ||||
// Graphs. | // Graphs. | ||||
graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }), | graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }), | ||||
noDataText: byText('project_activity.graphs.custom.no_history'), | noDataText: byText('project_activity.graphs.custom.no_history'), | ||||
gapInfoMessage: byText('project_activity.graphs.data_table.data_gap', { exact: false }), | |||||
// Add metrics. | // Add metrics. | ||||
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }), | addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }), | ||||
{ | { | ||||
metrics: keyBy( | metrics: keyBy( | ||||
[ | [ | ||||
mockMetric({ key: MetricKey.maintainability_issues, type: MetricType.Data }), | |||||
mockMetric({ key: MetricKey.bugs, type: MetricType.Integer }), | mockMetric({ key: MetricKey.bugs, type: MetricType.Integer }), | ||||
mockMetric({ key: MetricKey.code_smells, type: MetricType.Integer }), | mockMetric({ key: MetricKey.code_smells, type: MetricType.Integer }), | ||||
mockMetric({ key: MetricKey.security_hotspots_reviewed }), | mockMetric({ key: MetricKey.security_hotspots_reviewed }), |
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_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_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.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 | project_activity.custom_metric.covered_lines=Covered Lines | ||||