]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22699 Cache project and application overview metrics
authorIsmail Cherri <ismail.cherri@sonarsource.com>
Mon, 12 Aug 2024 12:11:28 +0000 (15:11 +0300)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Aug 2024 20:03:06 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/code/__tests__/__snapshots__/utils-test.tsx.snap
server/sonar-web/src/main/js/apps/code/utils.ts
server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
server/sonar-web/src/main/js/apps/overview/utils.tsx
server/sonar-web/src/main/js/apps/projects/utils.ts
server/sonar-web/src/main/js/queries/component.ts
server/sonar-web/src/main/js/queries/measures.ts
server/sonar-web/src/main/js/queries/quality-gates.ts
server/sonar-web/src/main/js/sonar-aligned/types/metrics.ts

index 25f3a9c6527277f62fbd961368ad88b522f483ba..bd05e186171ccc7ceb7e1aa95af02bcab27c257e 100644 (file)
@@ -19,26 +19,26 @@ exports[`getCodeMetrics should return the right metrics for apps 1`] = `
 exports[`getCodeMetrics should return the right metrics for portfolios 1`] = `
 [
   "releasability_rating",
-  "releasability_rating_new",
+  "software_quality_releasability_rating",
   "new_security_rating",
-  "new_security_rating_new",
+  "new_software_quality_security_rating",
   "new_reliability_rating",
-  "new_reliability_rating_new",
+  "new_software_quality_reliability_rating",
   "new_maintainability_rating",
-  "new_maintainability_rating_new",
+  "new_software_quality_maintainability_rating",
   "new_security_review_rating",
-  "new_security_review_rating_new",
+  "new_software_quality_security_review_rating",
   "new_lines",
   "releasability_rating",
-  "releasability_rating_new",
+  "software_quality_releasability_rating",
   "security_rating",
-  "security_rating_new",
+  "software_quality_security_rating",
   "reliability_rating",
-  "reliability_rating_new",
+  "software_quality_reliability_rating",
   "sqale_rating",
-  "sqale_rating_new",
+  "software_quality_maintainability_rating",
   "security_review_rating",
-  "security_review_rating_new",
+  "software_quality_security_review_rating",
   "ncloc",
 ]
 `;
@@ -46,26 +46,26 @@ exports[`getCodeMetrics should return the right metrics for portfolios 1`] = `
 exports[`getCodeMetrics should return the right metrics for portfolios 2`] = `
 [
   "releasability_rating",
-  "releasability_rating_new",
+  "software_quality_releasability_rating",
   "new_security_rating",
-  "new_security_rating_new",
+  "new_software_quality_security_rating",
   "new_reliability_rating",
-  "new_reliability_rating_new",
+  "new_software_quality_reliability_rating",
   "new_maintainability_rating",
-  "new_maintainability_rating_new",
+  "new_software_quality_maintainability_rating",
   "new_security_review_rating",
-  "new_security_review_rating_new",
+  "new_software_quality_security_review_rating",
   "new_lines",
   "releasability_rating",
-  "releasability_rating_new",
+  "software_quality_releasability_rating",
   "security_rating",
-  "security_rating_new",
+  "software_quality_security_rating",
   "reliability_rating",
-  "reliability_rating_new",
+  "software_quality_reliability_rating",
   "sqale_rating",
-  "sqale_rating_new",
+  "software_quality_maintainability_rating",
   "security_review_rating",
-  "security_review_rating_new",
+  "software_quality_security_review_rating",
   "ncloc",
   "alert_status",
 ]
@@ -74,15 +74,15 @@ exports[`getCodeMetrics should return the right metrics for portfolios 2`] = `
 exports[`getCodeMetrics should return the right metrics for portfolios 3`] = `
 [
   "releasability_rating",
-  "releasability_rating_new",
+  "software_quality_releasability_rating",
   "new_security_rating",
-  "new_security_rating_new",
+  "new_software_quality_security_rating",
   "new_reliability_rating",
-  "new_reliability_rating_new",
+  "new_software_quality_reliability_rating",
   "new_maintainability_rating",
-  "new_maintainability_rating_new",
+  "new_software_quality_maintainability_rating",
   "new_security_review_rating",
-  "new_security_review_rating_new",
+  "new_software_quality_security_review_rating",
   "new_lines",
   "alert_status",
 ]
@@ -91,15 +91,15 @@ exports[`getCodeMetrics should return the right metrics for portfolios 3`] = `
 exports[`getCodeMetrics should return the right metrics for portfolios 4`] = `
 [
   "releasability_rating",
-  "releasability_rating_new",
+  "software_quality_releasability_rating",
   "security_rating",
-  "security_rating_new",
+  "software_quality_security_rating",
   "reliability_rating",
-  "reliability_rating_new",
+  "software_quality_reliability_rating",
   "sqale_rating",
-  "sqale_rating_new",
+  "software_quality_maintainability_rating",
   "security_review_rating",
-  "security_review_rating_new",
+  "software_quality_security_review_rating",
   "ncloc",
   "alert_status",
 ]
index d64528288f3eb626f6fa6a228f6591a66ff8a8e3..a7056a18b264cb99b5750e8a2832731827d68e63 100644 (file)
@@ -37,29 +37,29 @@ const APPLICATION_METRICS = [MetricKey.alert_status, ...METRICS];
 
 const PORTFOLIO_METRICS = [
   MetricKey.releasability_rating,
-  MetricKey.releasability_rating_new,
+  MetricKey.software_quality_releasability_rating,
   MetricKey.security_rating,
-  MetricKey.security_rating_new,
+  MetricKey.software_quality_security_rating,
   MetricKey.reliability_rating,
-  MetricKey.reliability_rating_new,
+  MetricKey.software_quality_reliability_rating,
   MetricKey.sqale_rating,
-  MetricKey.sqale_rating_new,
+  MetricKey.software_quality_maintainability_rating,
   MetricKey.security_review_rating,
-  MetricKey.security_review_rating_new,
+  MetricKey.software_quality_security_review_rating,
   MetricKey.ncloc,
 ];
 
 const NEW_PORTFOLIO_METRICS = [
   MetricKey.releasability_rating,
-  MetricKey.releasability_rating_new,
+  MetricKey.software_quality_releasability_rating,
   MetricKey.new_security_rating,
-  MetricKey.new_security_rating_new,
+  MetricKey.new_software_quality_security_rating,
   MetricKey.new_reliability_rating,
-  MetricKey.new_reliability_rating_new,
+  MetricKey.new_software_quality_reliability_rating,
   MetricKey.new_maintainability_rating,
-  MetricKey.new_maintainability_rating_new,
+  MetricKey.new_software_quality_maintainability_rating,
   MetricKey.new_security_review_rating,
-  MetricKey.new_security_review_rating_new,
+  MetricKey.new_software_quality_security_review_rating,
   MetricKey.new_lines,
 ];
 
index 42c2e80fee06a0e36b61dce46b9ef119f07ae561..0c829116996021f628dbd7a0d1fe9ecc68377454 100644 (file)
@@ -25,12 +25,7 @@ import { MetricKey } from '~sonar-aligned/types/metrics';
 import { getApplicationDetails, getApplicationLeak } from '../../../api/application';
 import { getMeasuresWithPeriodAndMetrics } from '../../../api/measures';
 import { getProjectActivity } from '../../../api/projectActivity';
-import {
-  fetchQualityGate,
-  getApplicationQualityGate,
-  getGateForProject,
-  getQualityGateProjectStatus,
-} from '../../../api/quality-gates';
+import { fetchQualityGate, getGateForProject } from '../../../api/quality-gates';
 import { getAllTimeMachineData } from '../../../api/time-machine';
 import {
   getActivityGraph,
@@ -45,6 +40,11 @@ import {
   extractStatusConditionsFromProjectStatus,
 } from '../../../helpers/qualityGates';
 import { isDefined } from '../../../helpers/types';
+import { useMeasuresAndLeakQuery } from '../../../queries/measures';
+import {
+  useApplicationQualityGateStatus,
+  useProjectQualityGateStatus,
+} from '../../../queries/quality-gates';
 import { ApplicationPeriod } from '../../../types/application';
 import { Branch, BranchLike } from '../../../types/branch-like';
 import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity';
@@ -54,106 +54,122 @@ import '../styles.css';
 import { BRANCH_OVERVIEW_METRICS, HISTORY_METRICS_LIST, Status } from '../utils';
 import BranchOverviewRenderer from './BranchOverviewRenderer';
 
+// TODO: remove this once backend ready
+const NEW_METRICS = [
+  MetricKey.software_quality_maintainability_rating,
+  MetricKey.software_quality_security_rating,
+  MetricKey.new_software_quality_security_rating,
+  MetricKey.software_quality_reliability_rating,
+  MetricKey.new_software_quality_reliability_rating,
+  MetricKey.software_quality_security_review_rating,
+  MetricKey.new_software_quality_security_review_rating,
+  MetricKey.new_software_quality_maintainability_rating,
+];
+
 interface Props {
   branch?: Branch;
   branchesEnabled?: boolean;
   component: Component;
 }
 
-interface State {
-  analyses?: Analysis[];
-  appLeak?: ApplicationPeriod;
-  detectedCIOnLastAnalysis?: boolean;
-  graph: GraphType;
-  loadingHistory?: boolean;
-  loadingStatus?: boolean;
-  measures?: MeasureEnhanced[];
-  measuresHistory?: MeasureHistory[];
-  metrics?: Metric[];
-  period?: Period;
-  qgStatuses?: QualityGateStatus[];
-  qualityGate?: QualityGate;
-}
-
 export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph';
 export const NO_CI_DETECTED = 'undetected';
 
 // Get all history data over the past year.
 const FROM_DATE = toISO8601WithOffsetString(new Date().setFullYear(new Date().getFullYear() - 1));
 
-export default class BranchOverview extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State;
-
-  constructor(props: Props) {
-    super(props);
-
-    const { graph } = getActivityGraph(BRANCH_OVERVIEW_ACTIVITY_GRAPH, props.component.key);
-    this.state = { graph };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadStatus();
-    this.loadHistory();
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    if (prevProps.branch !== this.props.branch) {
-      this.loadStatus();
-      this.loadHistory();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadStatus = () => {
-    if (this.props.component.qualifier === ComponentQualifier.Application) {
-      this.loadApplicationStatus();
-    } else {
-      this.loadProjectStatus();
-      this.loadProjectQualityGate();
-    }
+export default function BranchOverview(props: Readonly<Props>) {
+  const { component, branch, branchesEnabled } = props;
+  const { graph: initialGraph } = getActivityGraph(
+    BRANCH_OVERVIEW_ACTIVITY_GRAPH,
+    props.component.key,
+  );
+  const [graph, setGraph] = React.useState<GraphType>(initialGraph);
+  const [loadingStatus, setLoadingStatus] = React.useState<boolean>(true);
+  const [appLeak, setAppLeak] = React.useState<ApplicationPeriod | undefined>(undefined);
+  const [measures, setMeasures] = React.useState<MeasureEnhanced[] | undefined>(undefined);
+  const [metrics, setMetrics] = React.useState<Metric[] | undefined>(undefined);
+  const [period, setPeriod] = React.useState<Period | undefined>(undefined);
+  const [qgStatuses, setQgStatuses] = React.useState<QualityGateStatus[] | undefined>(undefined);
+  const [loadingHistory, setLoadingHistory] = React.useState<boolean>(true);
+  const [analyses, setAnalyses] = React.useState<Analysis[] | undefined>(undefined);
+  const [detectedCIOnLastAnalysis, setDetectedCIOnLastAnalysis] = React.useState<
+    boolean | undefined
+  >(undefined);
+  const [qualityGate, setQualityGate] = React.useState<QualityGate | undefined>(undefined);
+  const [measuresHistory, setMeasuresHistory] = React.useState<MeasureHistory[] | undefined>(
+    undefined,
+  );
+
+  const { data: projectQualityGateStatus } = useProjectQualityGateStatus(
+    {
+      projectKey: component.key,
+      branchParameters: getBranchLikeQuery(branch),
+    },
+    { enabled: component.qualifier === ComponentQualifier.Project },
+  );
+
+  const { data: applicationQualityGateStatus } = useApplicationQualityGateStatus(
+    { application: component.key, ...getBranchLikeQuery(branch) },
+    { enabled: component.qualifier === ComponentQualifier.Application },
+  );
+
+  const { data: measuresAndLeak } = useMeasuresAndLeakQuery({
+    componentKey: component.key,
+    metricKeys:
+      component.qualifier === ComponentQualifier.Project
+        ? projectQualityGateStatus?.conditions !== undefined
+          ? uniq([
+              ...BRANCH_OVERVIEW_METRICS,
+              ...projectQualityGateStatus.conditions.map((c) => c.metricKey),
+            ])
+          : BRANCH_OVERVIEW_METRICS
+        : BRANCH_OVERVIEW_METRICS,
+    branchParameters: getBranchLikeQuery(branch),
+  });
+
+  const getEnhancedConditions = (
+    conditions: QualityGateStatusCondition[],
+    measures: MeasureEnhanced[],
+  ) => {
+    return (
+      conditions
+        // Enhance them with Metric information, which will be needed
+        // to render the conditions properly.
+        .map((c) => enhanceConditionWithMeasure(c, measures))
+        // The enhancement will return undefined if it cannot find the
+        // appropriate measure. Make sure we filter them out.
+        .filter(isDefined)
+    );
   };
 
-  loadApplicationStatus = async () => {
-    const { branch, component } = this.props;
-    this.setState({ loadingStatus: true });
-    // Start by loading the application quality gate info, as well as the meta
-    // data for the application as a whole.
-    const appStatus = await getApplicationQualityGate({
-      application: component.key,
-      ...getBranchLikeQuery(branch),
-    });
-    const {
-      measures: appMeasures,
-      metrics,
-      period,
-    } = await this.loadMeasuresAndMeta(component.key, branch);
+  const loadApplicationStatus = React.useCallback(async () => {
+    if (!measuresAndLeak || !applicationQualityGateStatus) {
+      return;
+    }
+    const { component: componentMeasures, metrics, period } = measuresAndLeak;
+    const appMeasures = componentMeasures.measures
+      ? enhanceMeasuresWithMetrics(componentMeasures.measures, metrics)
+      : [];
 
     const appBranchName =
       (branch && !isMainBranch(branch) && getBranchLikeDisplayName(branch)) || undefined;
 
     const appDetails = await getApplicationDetails(component.key, appBranchName);
 
+    setLoadingStatus(true);
     // We also need to load the application leak periods separately.
     getApplicationLeak(component.key, appBranchName).then(
       (leaks) => {
-        if (this.mounted && leaks && leaks.length) {
+        if (leaks && leaks.length) {
           const sortedLeaks = sortBy(leaks, (leak) => {
             return new Date(leak.date);
           });
-          this.setState({
-            appLeak: sortedLeaks[0],
-          });
+          setAppLeak(sortedLeaks[0]);
         }
       },
       () => {
-        if (this.mounted) {
-          this.setState({ appLeak: undefined });
-        }
+        setAppLeak(undefined);
       },
     );
 
@@ -162,13 +178,13 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
     // them at the parent application level will not get all the necessary
     // information, unfortunately, as they are aggregated.
     Promise.all(
-      appStatus.projects.map((project) => {
+      applicationQualityGateStatus.projects.map((project) => {
         const projectDetails = appDetails.projects.find((p) => p.key === project.key);
         const projectBranchLike = projectDetails
           ? { isMain: projectDetails.isMain, name: projectDetails.branch, excludedFromPurge: false }
           : undefined;
 
-        return this.loadMeasuresAndMeta(
+        return loadMeasuresAndMeta(
           project.key,
           projectBranchLike,
           // Only load metrics that apply to failing QG conditions; we don't
@@ -182,115 +198,95 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
       }),
     ).then(
       (results) => {
-        if (this.mounted) {
-          const qgStatuses = results
-            .map(({ measures = [], project, projectBranchLike }): QualityGateStatus => {
-              const { key, name, status, caycStatus } = project;
-              const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
-              const enhancedConditions = this.getEnhancedConditions(conditions, measures);
-              const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
-
-              return {
-                conditions: enhancedConditions,
-                failedConditions,
-                caycStatus,
-                key,
-                name,
-                status,
-                branchLike: projectBranchLike,
-              };
-            })
-            .sort((a, b) => Math.sign(b.failedConditions.length - a.failedConditions.length));
-
-          this.setState({
-            loadingStatus: false,
-            measures: appMeasures,
-            metrics,
-            period,
-            qgStatuses,
-          });
-        }
+        const qgStatuses = results
+          .map(({ measures = [], project, projectBranchLike }): QualityGateStatus => {
+            const { key, name, status, caycStatus } = project;
+            const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
+            const enhancedConditions = getEnhancedConditions(conditions, measures);
+            const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
+
+            return {
+              conditions: enhancedConditions,
+              failedConditions,
+              caycStatus,
+              key,
+              name,
+              status,
+              branchLike: projectBranchLike,
+            };
+          })
+          .sort((a, b) => Math.sign(b.failedConditions.length - a.failedConditions.length));
+
+        setQgStatuses(qgStatuses);
+        setPeriod(period);
+        setMetrics(metrics);
+        setMeasures(appMeasures);
+        setLoadingStatus(false);
       },
       () => {
-        if (this.mounted) {
-          this.setState({ loadingStatus: false, qgStatuses: undefined });
-        }
+        setQgStatuses(undefined);
+        setLoadingStatus(false);
       },
     );
-  };
+  }, [applicationQualityGateStatus, branch, component.key, measuresAndLeak]);
 
-  loadProjectStatus = async () => {
-    const {
-      branch,
-      component: { key, name },
-    } = this.props;
-    this.setState({ loadingStatus: true });
+  const loadProjectStatus = React.useCallback(() => {
+    const { key, name } = component;
 
-    const projectStatus = await getQualityGateProjectStatus({
-      projectKey: key,
-      ...getBranchLikeQuery(branch),
-    });
+    if (!measuresAndLeak || !projectQualityGateStatus) {
+      return;
+    }
+    setLoadingStatus(true);
+    const { component: componentMeasures, metrics, period } = measuresAndLeak;
+    const projectMeasures = componentMeasures.measures
+      ? enhanceMeasuresWithMetrics(componentMeasures.measures, metrics)
+      : [];
+
+    if (projectMeasures) {
+      const { ignoredConditions, caycStatus, status } = projectQualityGateStatus;
+      const conditions = extractStatusConditionsFromProjectStatus(projectQualityGateStatus);
+      const enhancedConditions = getEnhancedConditions(conditions, projectMeasures);
+      const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
+
+      const qgStatus: QualityGateStatus = {
+        ignoredConditions,
+        caycStatus,
+        conditions: enhancedConditions,
+        failedConditions,
+        key,
+        name,
+        status,
+        branchLike: branch,
+      };
 
-    // Get failing condition metric keys. We need measures for them as well to
-    // render them.
-    const metricKeys =
-      projectStatus.conditions !== undefined
-        ? uniq([...BRANCH_OVERVIEW_METRICS, ...projectStatus.conditions.map((c) => c.metricKey)])
-        : BRANCH_OVERVIEW_METRICS;
-
-    this.loadMeasuresAndMeta(key, branch, metricKeys).then(
-      ({ measures, metrics, period }) => {
-        if (this.mounted && measures) {
-          const { ignoredConditions, caycStatus, status } = projectStatus;
-          const conditions = extractStatusConditionsFromProjectStatus(projectStatus);
-          const enhancedConditions = this.getEnhancedConditions(conditions, measures);
-          const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
-
-          const qgStatus: QualityGateStatus = {
-            ignoredConditions,
-            caycStatus,
-            conditions: enhancedConditions,
-            failedConditions,
-            key,
-            name,
-            status,
-            branchLike: branch,
-          };
-
-          this.setState({
-            loadingStatus: false,
-            measures,
-            metrics,
-            period,
-            qgStatuses: [qgStatus],
-          });
-        } else if (this.mounted) {
-          this.setState({ loadingStatus: false, qgStatuses: undefined });
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loadingStatus: false, qgStatuses: undefined });
-        }
-      },
-    );
-  };
+      setMeasures(projectMeasures);
+      setMetrics(metrics);
+      setPeriod(period);
+      setQgStatuses([qgStatus]);
+    } else {
+      setQgStatuses(undefined);
+    }
+    setLoadingStatus(false);
+  }, [branch, component, measuresAndLeak, projectQualityGateStatus]);
 
-  loadProjectQualityGate = async () => {
-    const { component } = this.props;
+  const loadProjectQualityGate = React.useCallback(async () => {
     const qualityGate = await getGateForProject({ project: component.key });
     const qgDetails = await fetchQualityGate({ name: qualityGate.name });
-    this.setState({ qualityGate: qgDetails });
-  };
+    setQualityGate(qgDetails);
+  }, [component.key]);
 
-  loadMeasuresAndMeta = (
+  const loadMeasuresAndMeta = (
     componentKey: string,
     branchLike?: BranchLike,
     metricKeys: string[] = [],
   ) => {
     return getMeasuresWithPeriodAndMetrics(
       componentKey,
-      metricKeys.length > 0 ? metricKeys : BRANCH_OVERVIEW_METRICS,
+      metricKeys.length > 0
+        ? metricKeys
+        : BRANCH_OVERVIEW_METRICS.filter(
+            (metricKey) => !NEW_METRICS.includes(metricKey as MetricKey),
+          ),
       getBranchLikeQuery(branchLike),
     ).then(({ component: { measures }, metrics, period }) => {
       return {
@@ -301,19 +297,7 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
     });
   };
 
-  loadHistory = () => {
-    this.setState({ loadingHistory: true });
-
-    return Promise.all([this.loadHistoryMeasures(), this.loadAnalyses()]).then(
-      this.doneLoadingHistory,
-      this.doneLoadingHistory,
-    );
-  };
-
-  loadHistoryMeasures = () => {
-    const { branch, component } = this.props;
-    const { graph } = this.state;
-
+  const loadHistoryMeasures = React.useCallback(() => {
     const graphMetrics = getHistoryMetrics(graph, []);
     const metrics = uniq([...HISTORY_METRICS_LIST, ...graphMetrics]);
 
@@ -324,62 +308,21 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
       metrics: metrics.join(),
     }).then(
       ({ measures }) => {
-        if (this.mounted) {
-          this.setState({
-            measuresHistory: measures.map((measure) => ({
-              metric: measure.metric,
-              history: measure.history.map((analysis) => ({
-                date: parseDate(analysis.date),
-                value: analysis.value,
-              })),
+        setMeasuresHistory(
+          measures.map((measure) => ({
+            metric: measure.metric,
+            history: measure.history.map((analysis) => ({
+              date: parseDate(analysis.date),
+              value: analysis.value,
             })),
-          });
-        }
-      },
-      () => {},
-    );
-  };
-
-  loadAnalyses = () => {
-    const { branch } = this.props;
-
-    return getProjectActivity({
-      ...getBranchLikeQuery(branch),
-      project: this.getTopLevelComponent(),
-      from: FROM_DATE,
-    }).then(
-      ({ analyses }) => {
-        if (this.mounted) {
-          this.setState({
-            detectedCIOnLastAnalysis:
-              analyses.length > 0
-                ? analyses[0].detectedCI !== undefined && analyses[0].detectedCI !== NO_CI_DETECTED
-                : undefined,
-            analyses,
-          });
-        }
+          })),
+        );
       },
       () => {},
     );
-  };
-
-  getEnhancedConditions = (
-    conditions: QualityGateStatusCondition[],
-    measures: MeasureEnhanced[],
-  ) => {
-    return (
-      conditions
-        // Enhance them with Metric information, which will be needed
-        // to render the conditions properly.
-        .map((c) => enhanceConditionWithMeasure(c, measures))
-        // The enhancement will return undefined if it cannot find the
-        // appropriate measure. Make sure we filter them out.
-        .filter(isDefined)
-    );
-  };
+  }, [branch, component.key, graph]);
 
-  getTopLevelComponent = () => {
-    const { component } = this.props;
+  const getTopLevelComponent = React.useCallback(() => {
     let current = component.breadcrumbs.length - 1;
     while (
       current > 0 &&
@@ -394,68 +337,86 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
       current--;
     }
     return component.breadcrumbs[current].key;
-  };
+  }, [component.breadcrumbs]);
 
-  doneLoadingHistory = () => {
-    if (this.mounted) {
-      this.setState({
-        loadingHistory: false,
-      });
-    }
+  const loadAnalyses = React.useCallback(() => {
+    return getProjectActivity({
+      ...getBranchLikeQuery(branch),
+      project: getTopLevelComponent(),
+      from: FROM_DATE,
+    }).then(
+      ({ analyses }) => {
+        setAnalyses(analyses);
+        setDetectedCIOnLastAnalysis(
+          analyses.length > 0
+            ? analyses[0].detectedCI !== undefined && analyses[0].detectedCI !== NO_CI_DETECTED
+            : undefined,
+        );
+      },
+      () => {},
+    );
+  }, [branch, getTopLevelComponent]);
+
+  const loadHistory = React.useCallback(() => {
+    setLoadingHistory(true);
+
+    return Promise.all([loadHistoryMeasures(), loadAnalyses()]).then(
+      doneLoadingHistory,
+      doneLoadingHistory,
+    );
+  }, [loadAnalyses, loadHistoryMeasures]);
+
+  const doneLoadingHistory = () => {
+    setLoadingHistory(false);
   };
 
-  handleGraphChange = (graph: GraphType) => {
-    const { component } = this.props;
+  const handleGraphChange = (graph: GraphType) => {
+    setGraph(graph);
     saveActivityGraph(BRANCH_OVERVIEW_ACTIVITY_GRAPH, component.key, graph);
-    this.setState({ graph, loadingHistory: true }, () => {
-      this.loadHistoryMeasures().then(this.doneLoadingHistory, this.doneLoadingHistory);
-    });
+    setLoadingHistory(true);
+    loadHistoryMeasures().then(doneLoadingHistory, doneLoadingHistory);
   };
 
-  render() {
-    const { branch, branchesEnabled, component } = this.props;
-    const {
-      analyses,
-      appLeak,
-      detectedCIOnLastAnalysis,
-      graph,
-      loadingStatus,
-      loadingHistory,
-      measures,
-      measuresHistory,
-      metrics,
-      period,
-      qgStatuses,
-      qualityGate,
-    } = this.state;
-
-    const projectIsEmpty =
-      loadingStatus === false &&
-      (measures === undefined ||
-        measures.find((measure) =>
-          ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key),
-        ) === undefined);
-
-    return (
-      <BranchOverviewRenderer
-        analyses={analyses}
-        appLeak={appLeak}
-        branch={branch}
-        branchesEnabled={branchesEnabled}
-        component={component}
-        detectedCIOnLastAnalysis={detectedCIOnLastAnalysis}
-        graph={graph}
-        loadingHistory={loadingHistory}
-        loadingStatus={loadingStatus}
-        measures={measures}
-        measuresHistory={measuresHistory}
-        metrics={metrics}
-        onGraphChange={this.handleGraphChange}
-        period={period}
-        projectIsEmpty={projectIsEmpty}
-        qgStatuses={qgStatuses}
-        qualityGate={qualityGate}
-      />
-    );
-  }
+  const loadStatus = React.useCallback(() => {
+    if (component.qualifier === ComponentQualifier.Application) {
+      loadApplicationStatus();
+    } else {
+      loadProjectStatus();
+      loadProjectQualityGate();
+    }
+  }, [component.qualifier, loadApplicationStatus, loadProjectQualityGate, loadProjectStatus]);
+
+  React.useEffect(() => {
+    loadStatus();
+    loadHistory();
+  }, [branch, loadHistory, loadStatus]);
+
+  const projectIsEmpty =
+    loadingStatus === false &&
+    (measures === undefined ||
+      measures.find((measure) =>
+        ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key),
+      ) === undefined);
+
+  return (
+    <BranchOverviewRenderer
+      analyses={analyses}
+      appLeak={appLeak}
+      branch={branch}
+      branchesEnabled={branchesEnabled}
+      component={component}
+      detectedCIOnLastAnalysis={detectedCIOnLastAnalysis}
+      graph={graph}
+      loadingHistory={loadingHistory}
+      loadingStatus={loadingStatus}
+      measures={measures}
+      measuresHistory={measuresHistory}
+      metrics={metrics}
+      onGraphChange={handleGraphChange}
+      period={period}
+      projectIsEmpty={projectIsEmpty}
+      qgStatuses={qgStatuses}
+      qualityGate={qualityGate}
+    />
+  );
 }
index f502327d88d07694fdc5ce85788e237501008806..a3e938ee67c74a38a14da2a5b29df5699dfc617e 100644 (file)
@@ -28,6 +28,7 @@ import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
 import { MeasuresServiceMock } from '../../../../api/mocks/MeasuresServiceMock';
 import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
 import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
+import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
 import { TimeMachineServiceMock } from '../../../../api/mocks/TimeMachineServiceMock';
 import UsersServiceMock from '../../../../api/mocks/UsersServiceMock';
 import { PARENT_COMPONENT_KEY } from '../../../../api/mocks/data/ids';
@@ -42,6 +43,7 @@ import { mockAnalysis, mockAnalysisEvent } from '../../../../helpers/mocks/proje
 import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality-gates';
 import { mockLoggedInUser, mockMeasure, mockPaging } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { ComponentPropsType } from '../../../../helpers/testUtils';
 import { SoftwareImpactSeverity, SoftwareQuality } from '../../../../types/clean-code-taxonomy';
 import { ProjectAnalysisEventCategory } from '../../../../types/project-activity';
 import { CaycStatus } from '../../../../types/types';
@@ -49,6 +51,7 @@ import BranchOverview, { NO_CI_DETECTED } from '../BranchOverview';
 import { getPageObjects } from '../test-utils';
 
 const almHandler = new AlmSettingsServiceMock();
+const settingsHandler = new SettingsServiceMock();
 let branchesHandler: BranchesServiceMock;
 let measuresHandler: MeasuresServiceMock;
 let applicationHandler: ApplicationServiceMock;
@@ -124,6 +127,7 @@ afterEach(() => {
   timeMarchineHandler.reset();
   qualityGatesHandler.reset();
   almHandler.reset();
+  settingsHandler.reset();
 });
 
 describe('project overview', () => {
@@ -664,7 +668,7 @@ it.each([
 ])(
   "should correctly flag a project that wasn't analyzed using a CI (%s)",
   async (_, analyses, expected) => {
-    jest.mocked(getProjectActivity).mockResolvedValueOnce({ analyses, paging: mockPaging() });
+    jest.mocked(getProjectActivity).mockResolvedValue({ analyses, paging: mockPaging() });
 
     renderBranchOverview();
 
@@ -753,7 +757,7 @@ it.each([
       now: new Date('2023-04-25T12:00:00+0200'),
     });
 
-    jest.mocked(getProjectActivity).mockResolvedValueOnce({
+    jest.mocked(getProjectActivity).mockResolvedValue({
       analyses,
       paging: mockPaging(),
     });
@@ -775,7 +779,7 @@ it.each([
   },
 );
 
-function renderBranchOverview(props: Partial<BranchOverview['props']> = {}) {
+function renderBranchOverview(props: Partial<ComponentPropsType<typeof BranchOverview>> = {}) {
   const user = mockLoggedInUser();
   const component = mockComponent({
     breadcrumbs: [mockComponent({ key: 'foo' })],
index b2446f0dd89fe81b8df1132b88c7eea0416c20e4..7e34977780e36e14881f9a6f6cafafae61aa89cb 100644 (file)
@@ -50,13 +50,17 @@ export const BRANCH_OVERVIEW_METRICS: string[] = [
   MetricKey.bugs,
   MetricKey.new_bugs,
   MetricKey.reliability_rating,
+  MetricKey.software_quality_reliability_rating,
   MetricKey.new_reliability_rating,
+  MetricKey.new_software_quality_reliability_rating,
 
   // vulnerabilities
   MetricKey.vulnerabilities,
   MetricKey.new_vulnerabilities,
   MetricKey.security_rating,
+  MetricKey.software_quality_security_rating,
   MetricKey.new_security_rating,
+  MetricKey.new_software_quality_security_rating,
 
   // hotspots
   MetricKey.security_hotspots,
@@ -64,13 +68,17 @@ export const BRANCH_OVERVIEW_METRICS: string[] = [
   MetricKey.security_hotspots_reviewed,
   MetricKey.new_security_hotspots_reviewed,
   MetricKey.security_review_rating,
+  MetricKey.software_quality_security_review_rating,
   MetricKey.new_security_review_rating,
+  MetricKey.new_software_quality_security_review_rating,
 
   // code smells
   MetricKey.code_smells,
   MetricKey.new_code_smells,
   MetricKey.sqale_rating,
+  MetricKey.software_quality_maintainability_rating,
   MetricKey.new_maintainability_rating,
+  MetricKey.new_software_quality_maintainability_rating,
   MetricKey.sqale_index,
   MetricKey.new_technical_debt,
 
index ef28b77ef32e72e8621e22983207c275ce61d8a1..426e9195529d386d48d9d767f43537d052e6c3f3 100644 (file)
@@ -91,18 +91,18 @@ export const METRICS = [
   MetricKey.reliability_issues,
   MetricKey.bugs,
   MetricKey.reliability_rating,
-  MetricKey.reliability_rating_new,
+  MetricKey.software_quality_reliability_rating,
   MetricKey.security_issues,
   MetricKey.vulnerabilities,
   MetricKey.security_rating,
-  MetricKey.security_rating_new,
+  MetricKey.software_quality_security_rating,
   MetricKey.maintainability_issues,
   MetricKey.code_smells,
   MetricKey.sqale_rating,
-  MetricKey.sqale_rating_new,
+  MetricKey.software_quality_maintainability_rating,
   MetricKey.security_hotspots_reviewed,
   MetricKey.security_review_rating,
-  MetricKey.security_review_rating_new,
+  MetricKey.software_quality_security_review_rating,
   MetricKey.duplicated_lines_density,
   MetricKey.coverage,
   MetricKey.ncloc,
index f0b4c6e6da78faf233aa7c23b9d343db744a49cf..b3d6445a870e1af05a24f95504ad8567459fae81 100644 (file)
@@ -41,15 +41,15 @@ import { Component, Measure } from '../types/types';
 import { StaleTime, createInfiniteQueryHook, createQueryHook } from './common';
 
 const NEW_METRICS = [
-  MetricKey.sqale_rating_new,
-  MetricKey.security_rating_new,
-  MetricKey.reliability_rating_new,
-  MetricKey.security_review_rating_new,
-  MetricKey.releasability_rating_new,
-  MetricKey.new_security_rating_new,
-  MetricKey.new_reliability_rating_new,
-  MetricKey.new_maintainability_rating_new,
-  MetricKey.new_security_review_rating_new,
+  MetricKey.software_quality_maintainability_rating,
+  MetricKey.software_quality_security_rating,
+  MetricKey.software_quality_reliability_rating,
+  MetricKey.software_quality_security_review_rating,
+  MetricKey.software_quality_releasability_rating,
+  MetricKey.new_software_quality_security_rating,
+  MetricKey.new_software_quality_reliability_rating,
+  MetricKey.new_software_quality_maintainability_rating,
+  MetricKey.new_software_quality_security_review_rating,
 ];
 
 const TASK_RETRY = 10_000;
index 2c395cc740d8567f27e0af658ea737e7cdc22fa2..d574084311f80a61a9aaa195911ca680594f7ae1 100644 (file)
 import { queryOptions, useQuery, useQueryClient } from '@tanstack/react-query';
 import { groupBy } from 'lodash';
 import { BranchParameters } from '~sonar-aligned/types/branch-like';
-import { getMeasures, getMeasuresForProjects } from '../api/measures';
+import {
+  getMeasures,
+  getMeasuresForProjects,
+  getMeasuresWithPeriodAndMetrics,
+} from '../api/measures';
 import { getAllTimeMachineData } from '../api/time-machine';
 import { MetricKey } from '../sonar-aligned/types/metrics';
 import { Measure } from '../types/types';
 import { createQueryHook } from './common';
 
 const NEW_METRICS = [
-  MetricKey.sqale_rating_new,
-  MetricKey.security_rating_new,
-  MetricKey.reliability_rating_new,
-  MetricKey.security_review_rating_new,
+  MetricKey.software_quality_maintainability_rating,
+  MetricKey.software_quality_security_rating,
+  MetricKey.new_software_quality_security_rating,
+  MetricKey.software_quality_reliability_rating,
+  MetricKey.new_software_quality_reliability_rating,
+  MetricKey.software_quality_security_review_rating,
+  MetricKey.new_software_quality_security_review_rating,
+  MetricKey.new_software_quality_maintainability_rating,
 ];
 
 export function useAllMeasuresHistoryQuery(
@@ -84,6 +92,43 @@ export const useMeasuresForProjectsQuery = createQueryHook(
   },
 );
 
+export const useMeasuresAndLeakQuery = createQueryHook(
+  ({
+    componentKey,
+    metricKeys,
+    branchParameters,
+  }: {
+    branchParameters?: BranchParameters;
+    componentKey: string;
+    metricKeys: string[];
+  }) => {
+    const queryClient = useQueryClient();
+    return queryOptions({
+      queryKey: ['measures', 'details', 'component', componentKey, metricKeys, branchParameters],
+      queryFn: async () => {
+        // TODO remove this once all metrics are supported
+        const filteredMetricKeys = metricKeys.filter(
+          (metricKey) => !NEW_METRICS.includes(metricKey as MetricKey),
+        );
+        const { component, metrics, period } = await getMeasuresWithPeriodAndMetrics(
+          componentKey,
+          filteredMetricKeys,
+          branchParameters,
+        );
+        const measuresMapByMetricKey = groupBy(component.measures, 'metric');
+        metricKeys.forEach((metricKey) => {
+          const measure = measuresMapByMetricKey[metricKey]?.[0] ?? null;
+          queryClient.setQueryData<Measure>(
+            ['measures', 'details', componentKey, metricKey],
+            measure,
+          );
+        });
+        return { component, metrics, period };
+      },
+    });
+  },
+);
+
 export const useMeasureQuery = createQueryHook(
   ({ componentKey, metricKey }: { componentKey: string; metricKey: string }) => {
     return queryOptions({
index 9ba741783590fc54a10fd99481fd65e502f75ae3..1452aa2f05a57fac6905a9e990de74a84e553e82 100644 (file)
@@ -18,8 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import { addGlobalSuccessMessage } from 'design-system';
+import { BranchParameters } from '~sonar-aligned/types/branch-like';
 import {
   copyQualityGate,
   createCondition,
@@ -28,7 +29,9 @@ import {
   deleteQualityGate,
   fetchQualityGate,
   fetchQualityGates,
+  getApplicationQualityGate,
   getGateForProject,
+  getQualityGateProjectStatus,
   renameQualityGate,
   setQualityGateAsDefault,
   updateCondition,
@@ -36,6 +39,7 @@ import {
 import { getCorrectCaycCondition } from '../apps/quality-gates/utils';
 import { translate } from '../helpers/l10n';
 import { Condition, QualityGate } from '../types/types';
+import { createQueryHook } from './common';
 
 const QUERY_STALE_TIME = 5 * 60 * 1000;
 
@@ -250,3 +254,29 @@ export function useDeleteConditionMutation(gateName: string) {
     },
   });
 }
+
+export const useProjectQualityGateStatus = createQueryHook(
+  ({
+    projectId,
+    projectKey,
+    branchParameters,
+  }: {
+    branchParameters?: BranchParameters;
+    projectId?: string;
+    projectKey?: string;
+  }) => {
+    return queryOptions({
+      queryKey: ['quality-gate', 'status', 'project', projectId, projectKey, branchParameters],
+      queryFn: () => getQualityGateProjectStatus({ projectId, projectKey, ...branchParameters }),
+    });
+  },
+);
+
+export const useApplicationQualityGateStatus = createQueryHook(
+  ({ application, branch }: { application: string; branch?: string }) => {
+    return queryOptions({
+      queryKey: ['quality-gate', 'status', 'application', application, branch],
+      queryFn: () => getApplicationQualityGate({ application, branch }),
+    });
+  },
+);
index 684c9c2edcce4286522aa35d8a2963a43504b0b0..572db715de3982190eeedeea88b0a89d0cd941e6 100644 (file)
@@ -47,7 +47,7 @@ export enum MetricKey {
   duplicated_lines_density = 'duplicated_lines_density',
   duplications_data = 'duplications_data',
   effort_to_reach_maintainability_rating_a = 'effort_to_reach_maintainability_rating_a',
-  effort_to_reach_maintainability_rating_a_new = 'effort_to_reach_maintainability_rating_a_new',
+  effort_to_reach_software_quality_maintainability_rating_a = 'effort_to_reach_software_quality_maintainability_rating_a',
   executable_lines_data = 'executable_lines_data',
   false_positive_issues = 'false_positive_issues',
   file_complexity = 'file_complexity',
@@ -75,7 +75,7 @@ export enum MetricKey {
   lines_to_cover = 'lines_to_cover',
   maintainability_issues = 'maintainability_issues',
   maintainability_rating_distribution = 'maintainability_rating_distribution',
-  maintainability_rating_distribution_new = 'maintainability_rating_distribution_new',
+  software_quality_maintainability_rating_distribution = 'software_quality_maintainability_rating_distribution',
   maintainability_rating_effort = 'maintainability_rating_effort',
   major_violations = 'major_violations',
   minor_violations = 'minor_violations',
@@ -101,29 +101,29 @@ export enum MetricKey {
   new_lines_to_cover = 'new_lines_to_cover',
   new_maintainability_issues = 'new_maintainability_issues',
   new_maintainability_rating = 'new_maintainability_rating',
-  new_maintainability_rating_new = 'new_maintainability_rating_new',
+  new_software_quality_maintainability_rating = 'new_software_quality_maintainability_rating',
   new_maintainability_rating_distribution = 'new_maintainability_rating_distribution',
-  new_maintainability_rating_distribution_new = 'new_maintainability_rating_distribution_new',
+  new_software_quality_maintainability_rating_distribution = 'new_software_quality_maintainability_rating_distribution',
   new_major_violations = 'new_major_violations',
   new_minor_violations = 'new_minor_violations',
   new_reliability_issues = 'new_reliability_issues',
   new_reliability_rating = 'new_reliability_rating',
-  new_reliability_rating_new = 'new_reliability_rating_new',
+  new_software_quality_reliability_rating = 'new_software_quality_reliability_rating',
   new_reliability_rating_distribution = 'new_reliability_rating_distribution',
-  new_reliability_rating_distribution_new = 'new_reliability_rating_distribution_new',
+  new_software_quality_reliability_rating_distribution = 'new_software_quality_reliability_rating_distribution',
   new_reliability_remediation_effort = 'new_reliability_remediation_effort',
   new_security_hotspots = 'new_security_hotspots',
   new_security_hotspots_reviewed = 'new_security_hotspots_reviewed',
   new_security_issues = 'new_security_issues',
   new_security_rating = 'new_security_rating',
-  new_security_rating_new = 'new_security_rating_new',
+  new_software_quality_security_rating = 'new_software_quality_security_rating',
   new_security_rating_distribution = 'new_security_rating_distribution',
-  new_security_rating_distribution_new = 'new_security_rating_distribution_new',
+  new_software_quality_security_rating_distribution = 'new_software_quality_security_rating_distribution',
   new_security_remediation_effort = 'new_security_remediation_effort',
   new_security_review_rating = 'new_security_review_rating',
-  new_security_review_rating_new = 'new_security_review_rating_new',
+  new_software_quality_security_review_rating = 'new_software_quality_security_review_rating',
   new_security_review_rating_distribution = 'new_security_review_rating_distribution',
-  new_security_review_rating_distribution_new = 'new_security_review_rating_distribution_new',
+  new_software_quality_security_review_rating_distribution = 'new_software_quality_security_review_rating_distribution',
   new_sqale_debt_ratio = 'new_sqale_debt_ratio',
   new_technical_debt = 'new_technical_debt',
   new_uncovered_conditions = 'new_uncovered_conditions',
@@ -142,14 +142,14 @@ export enum MetricKey {
   quality_profiles = 'quality_profiles',
   releasability_effort = 'releasability_effort',
   releasability_rating = 'releasability_rating',
-  releasability_rating_new = 'releasability_rating_new',
+  software_quality_releasability_rating = 'software_quality_releasability_rating',
   releasability_rating_distribution = 'releasability_rating_distribution',
-  releasability_rating_distribution_new = 'releasability_rating_distribution_new',
+  software_quality_releasability_rating_distribution = 'software_quality_releasability_rating_distribution',
   reliability_issues = 'reliability_issues',
   reliability_rating = 'reliability_rating',
-  reliability_rating_new = 'reliability_rating_new',
+  software_quality_reliability_rating = 'software_quality_reliability_rating',
   reliability_rating_distribution = 'reliability_rating_distribution',
-  reliability_rating_distribution_new = 'reliability_rating_distribution_new',
+  software_quality_reliability_rating_distribution = 'software_quality_reliability_rating_distribution',
   reliability_rating_effort = 'reliability_rating_effort',
   reliability_remediation_effort = 'reliability_remediation_effort',
   reopened_issues = 'reopened_issues',
@@ -157,22 +157,22 @@ export enum MetricKey {
   security_hotspots_reviewed = 'security_hotspots_reviewed',
   security_issues = 'security_issues',
   security_rating = 'security_rating',
-  security_rating_new = 'security_rating_new',
+  software_quality_security_rating = 'software_quality_security_rating',
   security_rating_distribution = 'security_rating_distribution',
-  security_rating_distribution_new = 'security_rating_distribution_new',
+  software_quality_security_rating_distribution = 'software_quality_security_rating_distribution',
   security_rating_effort = 'security_rating_effort',
   security_remediation_effort = 'security_remediation_effort',
   security_review_rating = 'security_review_rating',
-  security_review_rating_new = 'security_review_rating_new',
+  software_quality_security_review_rating = 'software_quality_security_review_rating',
   security_review_rating_distribution = 'security_review_rating_distribution',
-  security_review_rating_distribution_new = 'security_review_rating_distribution_new',
+  software_quality_security_review_rating_distribution = 'software_quality_security_review_rating_distribution',
   security_review_rating_effort = 'security_review_rating_effort',
   skipped_tests = 'skipped_tests',
   sonarjava_feedback = 'sonarjava_feedback',
   sqale_debt_ratio = 'sqale_debt_ratio',
   sqale_index = 'sqale_index',
   sqale_rating = 'sqale_rating',
-  sqale_rating_new = 'sqale_rating_new',
+  software_quality_maintainability_rating = 'software_quality_maintainability_rating',
   statements = 'statements',
   team_at_sonarsource = 'team_at_sonarsource',
   team_size = 'team_size',