From dc10327ac17ca731c43dace970e0d63643d97932 Mon Sep 17 00:00:00 2001 From: David Cho-Lerat Date: Wed, 31 Jan 2024 12:22:28 +0100 Subject: [PATCH] SONAR-21547 Fix activity/hotspots page sometimes stuck loading --- .../components/ProjectActivityApp.tsx | 33 ++++++++++++++++--- .../security-hotspots/SecurityHotspotsApp.tsx | 12 +++++-- 2 files changed, 38 insertions(+), 7 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 122eacca422..92422ac7726 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 @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import * as React from 'react'; import { useSearchParams } from 'react-router-dom'; import { getApplicationLeak } from '../../../api/application'; @@ -38,7 +39,7 @@ import { isCustomGraph, } from '../../../components/activity-graph/utils'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; -import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like'; +import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { HIDDEN_METRICS } from '../../../helpers/constants'; import { parseDate } from '../../../helpers/dates'; import { serializeStringArray } from '../../../helpers/query'; @@ -94,6 +95,7 @@ class ProjectActivityApp extends React.PureComponent { constructor(props: Props) { super(props); + this.state = { analyses: [], analysesLoading: false, @@ -117,15 +119,15 @@ class ProjectActivityApp extends React.PureComponent { const hasQueryChanged = prevProps.location.query !== unparsedQuery; - const hasBranchChanged = !isSameBranchLike(prevProps.branchLike, this.props.branchLike); + const wasBranchJustFetched = !!prevProps.isFetchingBranch && !this.props.isFetchingBranch; - if (this.isBranchReady() && (hasBranchChanged || hasQueryChanged)) { + if (this.isBranchReady() && (hasQueryChanged || wasBranchJustFetched)) { const query = parseQuery(unparsedQuery); if ( query.graph !== this.state.query.graph || customMetricsChanged(this.state.query, query) || - hasBranchChanged + wasBranchJustFetched ) { if (this.state.initialized) { this.updateGraphData(query.graph || DEFAULT_GRAPH, query.customMetrics); @@ -133,6 +135,7 @@ class ProjectActivityApp extends React.PureComponent { this.firstLoadData(query, this.props.component); } } + this.setState({ query }); } } @@ -172,6 +175,7 @@ class ProjectActivityApp extends React.PureComponent { this.state.query.graph || DEFAULT_GRAPH, this.state.query.customMetrics, ); + this.setState(actions.deleteAnalysis(analysis)); } }); @@ -199,11 +203,13 @@ class ProjectActivityApp extends React.PureComponent { ps, ...getBranchLikeQuery(this.props.branchLike), }; + return getProjectActivity({ ...additional, ...parameters }).then(({ analyses, paging }) => ({ analyses: analyses.map((analysis) => ({ ...analysis, date: parseDate(analysis.date), })) as ParsedAnalysis[], + paging, })); }; @@ -212,6 +218,7 @@ class ProjectActivityApp extends React.PureComponent { if (metrics.length <= 0) { return Promise.resolve([]); } + return getAllTimeMachineData({ component: this.props.component.key, metrics: metrics.join(), @@ -219,6 +226,7 @@ class ProjectActivityApp extends React.PureComponent { }).then(({ measures }) => measures.map((measure) => ({ metric: measure.metric, + history: measure.history.map((analysis) => ({ date: parseDate(analysis.date), value: analysis.value, @@ -229,6 +237,7 @@ class ProjectActivityApp extends React.PureComponent { fetchAllActivities = (topLevelComponent: string) => { this.setState({ analysesLoading: true }); + this.loadAllActivities(topLevelComponent).then( ({ analyses }) => { if (this.mounted) { @@ -256,7 +265,9 @@ class ProjectActivityApp extends React.PureComponent { ) { return Promise.resolve(prevResult); } + const nextPage = prevResult ? prevResult.paging.pageIndex + 1 : 1; + return this.fetchActivity( project, [ @@ -269,6 +280,7 @@ class ProjectActivityApp extends React.PureComponent { if (!prevResult) { return this.loadAllActivities(project, result); } + return this.loadAllActivities(project, { analyses: prevResult.analyses.concat(result.analyses), paging: result.paging, @@ -278,6 +290,7 @@ class ProjectActivityApp extends React.PureComponent { getTopLevelComponent = (component: Component) => { let current = component.breadcrumbs.length - 1; + while ( current > 0 && !( @@ -290,6 +303,7 @@ class ProjectActivityApp extends React.PureComponent { ) { current--; } + return component.breadcrumbs[current].key; }; @@ -314,6 +328,7 @@ class ProjectActivityApp extends React.PureComponent { async firstLoadData(query: Query, component: Component) { const graphMetrics = getHistoryMetrics(query.graph || DEFAULT_GRAPH, query.customMetrics); const topLevelComponent = this.getTopLevelComponent(component); + try { const [{ analyses }, measuresHistory, leaks] = await Promise.all([ this.fetchActivity( @@ -326,7 +341,9 @@ class ProjectActivityApp extends React.PureComponent { 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) @@ -335,6 +352,7 @@ class ProjectActivityApp extends React.PureComponent { if (this.mounted) { let leakPeriodDate; + if (isApplication(component.qualifier) && leaks?.length) { [leakPeriodDate] = leaks .map((leak) => parseDate(leak.date)) @@ -363,6 +381,7 @@ class ProjectActivityApp extends React.PureComponent { updateGraphData = (graph: GraphType, customMetrics: string[]) => { const graphMetrics = getHistoryMetrics(graph, customMetrics); this.setState({ graphLoading: true }); + this.fetchMeasuresHistory(graphMetrics).then( (measuresHistory) => { if (this.mounted) { @@ -382,6 +401,7 @@ class ProjectActivityApp extends React.PureComponent { ...this.state.query, ...newQuery, }); + this.props.router.push({ pathname: this.props.location.pathname, query: { @@ -394,6 +414,7 @@ class ProjectActivityApp extends React.PureComponent { render() { const metrics = this.filterMetrics(); + return ( { const isFiltered = (searchParams: URLSearchParams) => { let filtered = false; + searchParams.forEach((value, key) => { if (key !== 'id' && value !== '') { filtered = true; } }); + return filtered; }; @@ -442,9 +465,11 @@ function RedirectWrapper(props: Props) { if (shouldRedirect) { const query = parseQuery(searchParams); const newQuery = { ...query, graph }; + if (isCustomGraph(newQuery.graph)) { searchParams.set('custom_metrics', customGraphs.join(',')); } + searchParams.set('graph', graph); setSearchParams(searchParams, { replace: true }); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx index ddcfbeb2f7d..2e85343ff49 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { flatMap, range } from 'lodash'; import * as React from 'react'; import { getMeasures } from '../../api/measures'; @@ -26,7 +27,7 @@ import withCurrentUserContext from '../../app/components/current-user/withCurren import withIndexationGuard from '../../components/hoc/withIndexationGuard'; import { Location, Router, withRouter } from '../../components/hoc/withRouter'; import { getLeakValue } from '../../components/measure/utils'; -import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../helpers/branch-like'; +import { getBranchLikeQuery, isPullRequest } from '../../helpers/branch-like'; import { isInput } from '../../helpers/keyboardEventHelpers'; import { KeyboardKeys } from '../../helpers/keycodes'; import { getStandards } from '../../helpers/security-standard'; @@ -89,6 +90,7 @@ export class SecurityHotspotsApp extends React.PureComponent { ...this.constructFiltersFromProps(props), status: HotspotStatusFilter.TO_REVIEW, }, + hotspots: [], hotspotsPageIndex: 1, hotspotsTotal: 0, @@ -96,6 +98,7 @@ export class SecurityHotspotsApp extends React.PureComponent { loadingMeasure: false, loadingMore: false, selectedHotspot: undefined, + standards: { [SecurityStandard.CWE]: {}, [SecurityStandard.OWASP_ASVS_4_0]: {}, @@ -119,8 +122,10 @@ export class SecurityHotspotsApp extends React.PureComponent { } componentDidUpdate(previous: Props) { + const wasBranchJustFetched = !!previous.isFetchingBranch && !this.props.isFetchingBranch; + if ( - !isSameBranchLike(this.props.branchLike, previous.branchLike) || + wasBranchJustFetched || this.props.component.key !== previous.component.key || this.props.location.query.hotspots !== previous.location.query.hotspots || SECURITY_STANDARDS.some((s) => this.props.location.query[s] !== previous.location.query[s]) || @@ -130,7 +135,7 @@ export class SecurityHotspotsApp extends React.PureComponent { } if ( - !isSameBranchLike(this.props.branchLike, previous.branchLike) || + wasBranchJustFetched || isLoggedIn(this.props.currentUser) !== isLoggedIn(previous.currentUser) || this.props.location.query.assignedToMe !== previous.location.query.assignedToMe || this.props.location.query.inNewCodePeriod !== previous.location.query.inNewCodePeriod @@ -337,6 +342,7 @@ export class SecurityHotspotsApp extends React.PureComponent { category: string; } | undefined; + filterByCWE: string | undefined; filterByFile: string | undefined; page: number; -- 2.39.5