aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/projectActivity
diff options
context:
space:
mode:
author7PH <benjamin.raymond@sonarsource.com>2023-09-19 15:25:18 +0200
committersonartech <sonartech@sonarsource.com>2023-09-25 20:02:47 +0000
commitd759f434ea84e43dc8c7d00642c0b11b9d6a19f0 (patch)
tree42f569320eb89f1cd9d525d6b9de0879b6a932aa /server/sonar-web/src/main/js/apps/projectActivity
parentf48dd4c12563e0026e0d1ab3c07b330ccf4d0d5e (diff)
downloadsonarqube-d759f434ea84e43dc8c7d00642c0b11b9d6a19f0.tar.gz
sonarqube-d759f434ea84e43dc8c7d00642c0b11b9d6a19f0.zip
SONAR-20438 Show new code period in application activity graphic
Diffstat (limited to 'server/sonar-web/src/main/js/apps/projectActivity')
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx87
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppRenderer.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx56
3 files changed, 114 insertions, 39 deletions
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
index 555837b63b6..c9a199e4b27 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
@@ -19,13 +19,14 @@
*/
import * as React from 'react';
import { useSearchParams } from 'react-router-dom';
+import { getApplicationLeak } from '../../../api/application';
import {
+ ProjectActivityStatuses,
changeEvent,
createEvent,
deleteAnalysis,
deleteEvent,
getProjectActivity,
- ProjectActivityStatuses,
} from '../../../api/projectActivity';
import { getAllTimeMachineData } from '../../../api/time-machine';
import withComponentContext from '../../../app/components/componentContext/withComponentContext';
@@ -42,7 +43,12 @@ import { parseDate } from '../../../helpers/dates';
import { serializeStringArray } from '../../../helpers/query';
import { withBranchLikes } from '../../../queries/branch';
import { BranchLike } from '../../../types/branch-like';
-import { ComponentQualifier, isPortfolioLike } from '../../../types/component';
+import {
+ ComponentQualifier,
+ isApplication,
+ isPortfolioLike,
+ isProject,
+} from '../../../types/component';
import { MetricKey } from '../../../types/metrics';
import {
GraphType,
@@ -53,9 +59,9 @@ import {
import { Component, Dict, Metric, Paging, RawQuery } from '../../../types/types';
import * as actions from '../actions';
import {
+ Query,
customMetricsChanged,
parseQuery,
- Query,
serializeQuery,
serializeUrlQuery,
} from '../utils';
@@ -72,6 +78,7 @@ interface Props {
export interface State {
analyses: ParsedAnalysis[];
analysesLoading: boolean;
+ leakPeriodDate?: Date;
graphLoading: boolean;
initialized: boolean;
measuresHistory: MeasureHistory[];
@@ -287,40 +294,53 @@ class ProjectActivityApp extends React.PureComponent<Props, State> {
);
};
- firstLoadData(query: Query, component: Component) {
+ async firstLoadData(query: Query, component: Component) {
const graphMetrics = getHistoryMetrics(query.graph || DEFAULT_GRAPH, query.customMetrics);
const topLevelComponent = this.getTopLevelComponent(component);
- Promise.all([
- this.fetchActivity(
- topLevelComponent,
- [
- ProjectActivityStatuses.STATUS_PROCESSED,
- ProjectActivityStatuses.STATUS_LIVE_MEASURE_COMPUTE,
- ],
- 1,
- ACTIVITY_PAGE_SIZE_FIRST_BATCH,
- serializeQuery(query),
- ),
- this.fetchMeasuresHistory(graphMetrics),
- ]).then(
- ([{ analyses }, measuresHistory]) => {
- if (this.mounted) {
- this.setState({
- analyses,
- graphLoading: false,
- initialized: true,
- measuresHistory,
- });
+ try {
+ const [{ analyses }, measuresHistory, leaks] = await Promise.all([
+ this.fetchActivity(
+ topLevelComponent,
+ [
+ ProjectActivityStatuses.STATUS_PROCESSED,
+ ProjectActivityStatuses.STATUS_LIVE_MEASURE_COMPUTE,
+ ],
+ 1,
+ ACTIVITY_PAGE_SIZE_FIRST_BATCH,
+ serializeQuery(query),
+ ),
+ this.fetchMeasuresHistory(graphMetrics),
+ component.qualifier === ComponentQualifier.Application
+ ? // eslint-disable-next-line local-rules/no-api-imports
+ getApplicationLeak(component.key)
+ : undefined,
+ ]);
- this.fetchAllActivities(topLevelComponent);
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ initialized: true, graphLoading: false });
+ if (this.mounted) {
+ let leakPeriodDate;
+ if (isApplication(component.qualifier) && leaks?.length) {
+ [leakPeriodDate] = leaks
+ .map((leak) => parseDate(leak.date))
+ .sort((d1, d2) => d2.getTime() - d1.getTime());
+ } else if (isProject(component.qualifier) && component.leakPeriodDate) {
+ leakPeriodDate = parseDate(component.leakPeriodDate);
}
- },
- );
+
+ this.setState({
+ analyses,
+ graphLoading: false,
+ initialized: true,
+ leakPeriodDate,
+ measuresHistory,
+ });
+
+ this.fetchAllActivities(topLevelComponent);
+ }
+ } catch (error) {
+ if (this.mounted) {
+ this.setState({ initialized: true, graphLoading: false });
+ }
+ }
}
updateGraphData = (graph: GraphType, customMetrics: string[]) => {
@@ -367,6 +387,7 @@ class ProjectActivityApp extends React.PureComponent<Props, State> {
onDeleteAnalysis={this.handleDeleteAnalysis}
onDeleteEvent={this.handleDeleteEvent}
graphLoading={!this.state.initialized || this.state.graphLoading}
+ leakPeriodDate={this.state.leakPeriodDate}
initializing={!this.state.initialized}
measuresHistory={this.state.measuresHistory}
metrics={metrics}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppRenderer.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppRenderer.tsx
index 7ccda994368..57080e0eda6 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppRenderer.tsx
@@ -47,6 +47,7 @@ interface Props {
onDeleteAnalysis: (analysis: string) => Promise<void>;
onDeleteEvent: (analysis: string, event: string) => Promise<void>;
graphLoading: boolean;
+ leakPeriodDate?: Date;
initializing: boolean;
project: Pick<Component, 'configuration' | 'key' | 'leakPeriodDate' | 'qualifier'>;
metrics: Metric[];
@@ -63,6 +64,7 @@ export default function ProjectActivityAppRenderer(props: Props) {
props.project.qualifier === ComponentQualifier.Application) &&
(configuration ? configuration.showHistory : false);
const canDeleteAnalyses = configuration ? configuration.showHistory : false;
+ const leakPeriodDate = props.leakPeriodDate ? parseDate(props.leakPeriodDate) : undefined;
return (
<main className="sw-p-5" id="project-activity">
<Suggestions suggestions="project_activity" />
@@ -92,9 +94,7 @@ export default function ProjectActivityAppRenderer(props: Props) {
onDeleteAnalysis={props.onDeleteAnalysis}
onDeleteEvent={props.onDeleteEvent}
initializing={props.initializing}
- leakPeriodDate={
- props.project.leakPeriodDate ? parseDate(props.project.leakPeriodDate) : undefined
- }
+ leakPeriodDate={leakPeriodDate}
project={props.project}
query={query}
onUpdateQuery={props.onUpdateQuery}
@@ -103,9 +103,7 @@ export default function ProjectActivityAppRenderer(props: Props) {
<StyledWrapper className="sw-col-span-8 sw-rounded-1">
<ProjectActivityGraphs
analyses={analyses}
- leakPeriodDate={
- props.project.leakPeriodDate ? parseDate(props.project.leakPeriodDate) : undefined
- }
+ leakPeriodDate={leakPeriodDate}
loading={props.graphLoading}
measuresHistory={measuresHistory}
metrics={props.metrics}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx
index f2ad64252b7..3202be860fb 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx
@@ -24,6 +24,7 @@ import { keyBy, times } from 'lodash';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { Route } from 'react-router-dom';
+import ApplicationServiceMock from '../../../../api/mocks/ApplicationServiceMock';
import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
import { TimeMachineServiceMock } from '../../../../api/mocks/TimeMachineServiceMock';
import { parseDate } from '../../../../helpers/dates';
@@ -56,11 +57,13 @@ jest.mock('../../../../helpers/storage', () => ({
save: jest.fn(),
}));
+const applicationHandler = new ApplicationServiceMock();
const projectActivityHandler = new ProjectActivityServiceMock();
const timeMachineHandler = new TimeMachineServiceMock();
beforeEach(() => {
jest.clearAllMocks();
+ applicationHandler.reset();
projectActivityHandler.reset();
timeMachineHandler.reset();
@@ -93,6 +96,56 @@ describe('rendering', () => {
expect(ui.graphs.getAll().length).toBe(1);
});
+ it('should render new code legend for applications', async () => {
+ const { ui } = getPageObject();
+
+ renderProjectActivityAppContainer(
+ mockComponent({
+ qualifier: ComponentQualifier.Application,
+ breadcrumbs: [
+ { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Application },
+ ],
+ }),
+ );
+ await ui.appLoaded();
+
+ expect(ui.newCodeLegend.get()).toBeInTheDocument();
+ });
+
+ it('should render new code legend for projects', async () => {
+ const { ui } = getPageObject();
+
+ renderProjectActivityAppContainer(
+ mockComponent({
+ qualifier: ComponentQualifier.Project,
+ breadcrumbs: [
+ { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
+ ],
+ leakPeriodDate: parseDate('2017-03-01T22:00:00.000Z').toDateString(),
+ }),
+ );
+ await ui.appLoaded();
+
+ expect(ui.newCodeLegend.get()).toBeInTheDocument();
+ });
+
+ it.each([ComponentQualifier.Portfolio, ComponentQualifier.SubPortfolio])(
+ 'should not render new code legend for %s',
+ async (qualifier) => {
+ const { ui } = getPageObject();
+
+ renderProjectActivityAppContainer(
+ mockComponent({
+ qualifier,
+ breadcrumbs: [{ key: 'breadcrumb', name: 'breadcrumb', qualifier }],
+ }),
+ );
+ await ui.appLoaded();
+
+ expect(ui.newCodeLegend.query()).not.toBeInTheDocument();
+ },
+ );
+
it('should correctly show the baseline marker', async () => {
const { ui } = getPageObject();
@@ -417,6 +470,9 @@ function getPageObject() {
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
metricCheckbox: (name: MetricKey) => byRole('checkbox', { name }),
+ // Graph legend.
+ newCodeLegend: byText('hotspot.filters.period.since_leak_period'),
+
// Filtering.
categorySelect: byLabelText('project_activity.filter_events'),
resetDatesBtn: byRole('button', { name: 'project_activity.reset_dates' }),