/*
* SonarQube
* Copyright (C) 2009-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import {
useComponent,
useTopLevelComponentKey,
} from '../../../app/components/componentContext/withComponentContext';
import { useMetrics } from '../../../app/components/metrics/withMetricsContext';
import {
DEFAULT_GRAPH,
getActivityGraph,
getHistoryMetrics,
isCustomGraph,
} from '../../../components/activity-graph/utils';
import { useLocation, useRouter } from '../../../components/hoc/withRouter';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { HIDDEN_METRICS } from '../../../helpers/constants';
import { parseDate } from '../../../helpers/dates';
import useApplicationLeakQuery from '../../../queries/applications';
import { useBranchesQuery } from '../../../queries/branch';
import { useAllMeasuresHistoryQuery } from '../../../queries/measures';
import { useAllProjectAnalysesQuery } from '../../../queries/project-analyses';
import { isApplication, isPortfolioLike, isProject } from '../../../types/component';
import { MetricKey } from '../../../types/metrics';
import { MeasureHistory, ParsedAnalysis } from '../../../types/project-activity';
import { Query, parseQuery, serializeUrlQuery } from '../utils';
import ProjectActivityAppRenderer from './ProjectActivityAppRenderer';
export interface State {
analyses: ParsedAnalysis[];
analysesLoading: boolean;
leakPeriodDate?: Date;
graphLoading: boolean;
initialized: boolean;
measuresHistory: MeasureHistory[];
query: Query;
}
export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph';
export function ProjectActivityApp() {
const { query, pathname } = useLocation();
const parsedQuery = parseQuery(query);
const router = useRouter();
const { component } = useComponent();
const metrics = useMetrics();
const { data: { branchLike } = {}, isFetching: isFetchingBranch } = useBranchesQuery(component);
const enabled =
component?.key !== undefined &&
(isPortfolioLike(component?.qualifier) || (Boolean(branchLike) && !isFetchingBranch));
const componentKey = useTopLevelComponentKey();
const { data: appLeaks } = useApplicationLeakQuery(
componentKey ?? '',
isApplication(component?.qualifier),
);
const { data: analysesData, isLoading: isLoadingAnalyses } = useAllProjectAnalysesQuery(enabled);
const { data: historyData, isLoading: isLoadingHistory } = useAllMeasuresHistoryQuery(
componentKey,
getBranchLikeQuery(branchLike),
getHistoryMetrics(query.graph || DEFAULT_GRAPH, parsedQuery.customMetrics).join(','),
enabled,
);
const analyses = React.useMemo(() => analysesData ?? [], [analysesData]);
const measuresHistory = React.useMemo(
() =>
historyData?.measures?.map((measure) => ({
metric: measure.metric,
history: measure.history.map((historyItem) => ({
date: parseDate(historyItem.date),
value: historyItem.value,
})),
})) ?? [],
[historyData],
);
const leakPeriodDate = React.useMemo(() => {
if (appLeaks?.[0]) {
return parseDate(appLeaks[0].date);
} else if (isProject(component?.qualifier) && component?.leakPeriodDate !== undefined) {
return parseDate(component.leakPeriodDate);
}
return undefined;
}, [appLeaks, component?.leakPeriodDate, component?.qualifier]);
const filteredMetrics = React.useMemo(() => {
if (isPortfolioLike(component?.qualifier)) {
return Object.values(metrics).filter(
(metric) => metric.key !== MetricKey.security_hotspots_reviewed,
);
}
return Object.values(metrics).filter(
(metric) =>
![...HIDDEN_METRICS, MetricKey.security_review_rating].includes(metric.key as MetricKey),
);
}, [component?.qualifier, metrics]);
const handleUpdateQuery = (newQuery: Query) => {
const q = serializeUrlQuery({
...parsedQuery,
...newQuery,
});
router.push({
pathname,
query: {
...q,
...getBranchLikeQuery(branchLike),
id: component?.key,
},
});
};
return (
component && (
)
);
}
export default function RedirectWrapper() {
const { query } = useLocation();
const { component } = useComponent();
const router = useRouter();
const filtered = React.useMemo(() => {
for (const key in query) {
if (key !== 'id' && query[key] !== '') {
return true;
}
}
return false;
}, [query]);
const { graph, customGraphs } = getActivityGraph(PROJECT_ACTIVITY_GRAPH, component?.key ?? '');
const emptyCustomGraph = isCustomGraph(graph) && customGraphs.length <= 0;
// if there is no filter, but there are saved preferences in the localStorage
// also don't redirect to custom if there is no metrics selected for it
const shouldRedirect = !filtered && graph != null && graph !== DEFAULT_GRAPH && !emptyCustomGraph;
React.useEffect(() => {
if (shouldRedirect) {
const newQuery = { ...query, graph };
if (isCustomGraph(newQuery.graph)) {
router.replace({ query: { ...newQuery, custom_metrics: customGraphs.join(',') } });
} else {
router.replace({ query: newQuery });
}
}
}, [shouldRedirect, router, query, graph, customGraphs]);
return shouldRedirect ? null : ;
}