]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21293 Move PullRequest Overview to react-query
author7PH <benjamin.raymond@sonarsource.com>
Thu, 14 Dec 2023 13:12:51 +0000 (14:12 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 14 Dec 2023 20:02:59 +0000 (20:02 +0000)
server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts
server/sonar-web/src/main/js/api/quality-gates.ts
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx
server/sonar-web/src/main/js/queries/component.ts
server/sonar-web/src/main/js/queries/quality-gates.ts
server/sonar-web/src/main/js/types/types.ts

index cfb2574674cbef8dd89fc85b766cb5412dbd07c4..eb935c59857f8e5ba847473f271daf45b3036678 100644 (file)
@@ -281,7 +281,7 @@ export class QualityGatesServiceMock {
     jest.mocked(associateGateWithProject).mockImplementation(this.selectHandler);
     jest.mocked(dissociateGateWithProject).mockImplementation(this.deSelectHandler);
     jest.mocked(setQualityGateAsDefault).mockImplementation(this.setDefaultHandler);
-    (getGateForProject as jest.Mock).mockImplementation(this.projectGateHandler);
+    jest.mocked(getGateForProject).mockImplementation(this.projectGateHandler);
     jest.mocked(getQualityGateProjectStatus).mockImplementation(this.handleQualityGetProjectStatus);
     jest.mocked(getApplicationQualityGate).mockImplementation(this.handleGetApplicationQualityGate);
 
@@ -549,7 +549,16 @@ export class QualityGatesServiceMock {
       return Promise.reject('unknown');
     }
 
-    return this.reply(this.list.find((qg) => qg.name === this.getGateForProjectGateName));
+    const qualityGate = this.list.find((qg) => qg.name === this.getGateForProjectGateName);
+
+    if (!qualityGate) {
+      return Promise.reject('Unable to find quality gate');
+    }
+
+    return this.reply({
+      name: qualityGate.name,
+      isDefault: qualityGate.isDefault,
+    });
   };
 
   handleGetApplicationQualityGate = () => {
index 819efc454331d9134ef97771e25722238aafca22..7d6bfc641871f40379cbc8d57431bf48fdaef8ab 100644 (file)
@@ -28,7 +28,7 @@ import {
   QualityGateProjectStatus,
   SearchPermissionsParameters,
 } from '../types/quality-gates';
-import { Condition, Paging, QualityGate } from '../types/types';
+import { Condition, Paging, QualityGate, QualityGatePreview } from '../types/types';
 import { UserBase } from '../types/users';
 
 export function fetchQualityGates(): Promise<{
@@ -81,7 +81,7 @@ export function deleteCondition(data: { id: string }): Promise<void> {
   return post('/api/qualitygates/delete_condition', data);
 }
 
-export function getGateForProject(data: { project: string }): Promise<QualityGate> {
+export function getGateForProject(data: { project: string }): Promise<QualityGatePreview> {
   return getJSON('/api/qualitygates/get_by_project', data).then(
     ({ qualityGate }) => ({
       ...qualityGate,
index 40cc145f7093ee7915ae1346e621147ed0fadd35..d949204ffc93a35e7ae0a4b3de9cdbcafd2b15af 100644 (file)
 import { BasicSeparator, CenteredLayout, PageContentFontWrapper, Spinner } from 'design-system';
 import { uniq } from 'lodash';
 import * as React from 'react';
-import { useEffect, useState } from 'react';
-import { getMeasuresWithMetrics } from '../../../api/measures';
-import { fetchQualityGate, getGateForProject } from '../../../api/quality-gates';
 import { getBranchLikeQuery } from '../../../helpers/branch-like';
 import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures';
 import { isDefined } from '../../../helpers/types';
 import { useBranchStatusQuery } from '../../../queries/branch';
+import { useComponentMeasuresWithMetricsQuery } from '../../../queries/component';
+import { useComponentQualityGateQuery } from '../../../queries/quality-gates';
 import { PullRequest } from '../../../types/branch-like';
-import { Component, MeasureEnhanced, QualityGate } from '../../../types/types';
+import { Component } from '../../../types/types';
 import BranchQualityGate from '../components/BranchQualityGate';
 import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
 import MetaTopBar from '../components/MetaTopBar';
@@ -43,53 +42,34 @@ interface Props {
   component: Component;
 }
 
-export default function PullRequestOverview(props: Props) {
+export default function PullRequestOverview(props: Readonly<Props>) {
   const { component, branchLike } = props;
-  const [isLoadingMeasures, setIsLoadingMeasures] = useState(false);
-  const [measures, setMeasures] = useState<MeasureEnhanced[]>([]);
+
   const {
     data: { conditions, ignoredConditions, status } = {},
     isLoading: isLoadingBranchStatusesData,
   } = useBranchStatusQuery(component);
-  const [isLoadingQualityGate, setIsLoadingQualityGate] = useState(false);
-  const [qualityGate, setQualityGate] = useState<QualityGate>();
-  const isLoading = isLoadingBranchStatusesData || isLoadingMeasures || isLoadingQualityGate;
-
-  useEffect(() => {
-    setIsLoadingMeasures(true);
-
-    const metricKeys =
-      conditions !== undefined
-        ? // Also load metrics that apply to QG conditions.
-          uniq([...PR_METRICS, ...conditions.map((c) => c.metric)])
-        : PR_METRICS;
-
-    getMeasuresWithMetrics(component.key, metricKeys, getBranchLikeQuery(branchLike)).then(
-      ({ component, metrics }) => {
-        if (component.measures) {
-          setIsLoadingMeasures(false);
-          setMeasures(enhanceMeasuresWithMetrics(component.measures || [], metrics));
-        }
-      },
-      () => {
-        setIsLoadingMeasures(false);
-      },
-    );
-  }, [branchLike, component.key, conditions]);
 
-  useEffect(() => {
-    async function fetchQualityGateDate() {
-      setIsLoadingQualityGate(true);
+  const { data: qualityGate, isLoading: isLoadingQualityGate } = useComponentQualityGateQuery(
+    component.key,
+  );
 
-      const qualityGate = await getGateForProject({ project: component.key });
-      const qgDetails = await fetchQualityGate({ name: qualityGate.name });
+  const { data: componentMeasures, isLoading: isLoadingMeasures } =
+    useComponentMeasuresWithMetricsQuery(
+      component.key,
+      uniq([...PR_METRICS, ...(conditions?.map((c) => c.metric) ?? [])]),
+      getBranchLikeQuery(branchLike),
+      !isLoadingBranchStatusesData,
+    );
 
-      setQualityGate(qgDetails);
-      setIsLoadingQualityGate(false);
-    }
+  const measures = componentMeasures
+    ? enhanceMeasuresWithMetrics(
+        componentMeasures.component.measures ?? [],
+        componentMeasures.metrics,
+      )
+    : [];
 
-    fetchQualityGateDate();
-  }, [component.key]);
+  const isLoading = isLoadingBranchStatusesData || isLoadingMeasures || isLoadingQualityGate;
 
   if (isLoading) {
     return (
index 6d9a4d3236203225a61e5fe212ed1ec79c9925c8..1dac130a0c096f045de42948097235c56298c7bb 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { useQuery } from '@tanstack/react-query';
+import { UseQueryResult, useQuery } from '@tanstack/react-query';
 import { getTasksForComponent } from '../api/ce';
+import { getMeasuresWithMetrics } from '../api/measures';
+import { BranchParameters } from '../types/branch-like';
+import { MeasuresAndMetaWithMetrics } from '../types/measures';
 import { Component } from '../types/types';
 
 const TASK_RETRY = 10_000;
 
+type QueryKeyData = {
+  metricKeys: string[];
+  branchParameters: BranchParameters;
+};
+
+function getComponentQueryKey(key: string, type: 'tasks'): string[];
+function getComponentQueryKey(key: string, type: 'measures', data: QueryKeyData): string[];
+function getComponentQueryKey(key: string, type: string, data?: QueryKeyData): string[] {
+  return ['component', key, type, JSON.stringify(data)];
+}
+
+function extractQueryKeyData(queryKey: string[]): { key: string; data?: QueryKeyData } {
+  const [, key, , data] = queryKey;
+  return { key, data: JSON.parse(data ?? 'null') };
+}
+
 export function useTaskForComponentQuery(component: Component) {
   return useQuery({
-    queryKey: ['component', component.key, 'tasks'] as const,
-    queryFn: ({ queryKey: [_, key] }) => getTasksForComponent(key),
+    queryKey: getComponentQueryKey(component.key, 'tasks'),
+    queryFn: ({ queryKey }) => {
+      const { key } = extractQueryKeyData(queryKey);
+      return getTasksForComponent(key);
+    },
     refetchInterval: TASK_RETRY,
   });
 }
+
+export function useComponentMeasuresWithMetricsQuery(
+  key: string,
+  metricKeys: string[],
+  branchParameters: BranchParameters,
+  enabled = true,
+): UseQueryResult<MeasuresAndMetaWithMetrics> {
+  return useQuery({
+    enabled,
+    queryKey: getComponentQueryKey(key, 'measures', {
+      metricKeys,
+      branchParameters,
+    }),
+    queryFn: ({ queryKey }) => {
+      const { key, data } = extractQueryKeyData(queryKey);
+      return data && getMeasuresWithMetrics(key, data.metricKeys, data.branchParameters);
+    },
+  });
+}
index 79e6fe47557bd20bbc5390422561c6e9ec916558..013b95000f35a4876a1f6c53995db9bfc1c21b6a 100644 (file)
@@ -26,6 +26,7 @@ import {
   deleteQualityGate,
   fetchQualityGate,
   fetchQualityGates,
+  getGateForProject,
   renameQualityGate,
   setQualityGateAsDefault,
   updateCondition,
@@ -35,21 +36,35 @@ import { addGlobalSuccessMessage } from '../helpers/globalMessages';
 import { translate } from '../helpers/l10n';
 import { Condition, QualityGate } from '../types/types';
 
-const QUALITY_GATE_KEY = 'quality-gate';
-const QUALITY_GATES_KEY = 'quality-gates';
+function getQualityGateQueryKey(type: 'name', data: string): string[];
+function getQualityGateQueryKey(type: 'project', data: string): string[];
+function getQualityGateQueryKey(): string[];
+function getQualityGateQueryKey(type?: string, data?: string) {
+  return ['quality-gate', type ?? '', data ?? ''].filter(Boolean);
+}
 
 export function useQualityGateQuery(name: string) {
   return useQuery({
-    queryKey: [QUALITY_GATE_KEY, name] as const,
-    queryFn: ({ queryKey: [_, name] }) => {
+    queryKey: getQualityGateQueryKey('name', name),
+    queryFn: ({ queryKey: [, name] }) => {
       return fetchQualityGate({ name });
     },
   });
 }
 
+export function useComponentQualityGateQuery(project: string) {
+  return useQuery({
+    queryKey: getQualityGateQueryKey('project', project),
+    queryFn: async ({ queryKey: [, , project] }) => {
+      const qualityGatePreview = await getGateForProject({ project });
+      return fetchQualityGate({ name: qualityGatePreview.name });
+    },
+  });
+}
+
 export function useQualityGatesQuery() {
   return useQuery({
-    queryKey: [QUALITY_GATES_KEY] as const,
+    queryKey: getQualityGateQueryKey(),
     queryFn: () => {
       return fetchQualityGates();
     },
@@ -65,7 +80,7 @@ export function useCreateQualityGateMutation() {
       return createQualityGate({ name });
     },
     onSuccess: () => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
     },
   });
 }
@@ -78,8 +93,8 @@ export function useSetQualityGateAsDefaultMutation(gateName: string) {
       return setQualityGateAsDefault({ name: qualityGate.name });
     },
     onSuccess: () => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
-      queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
+      queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
     },
   });
 }
@@ -92,8 +107,8 @@ export function useRenameQualityGateMutation(currentName: string) {
       return renameQualityGate({ currentName, name: newName });
     },
     onSuccess: (_, newName: string) => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
-      queryClient.invalidateQueries([QUALITY_GATE_KEY, newName]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
+      queryClient.invalidateQueries(getQualityGateQueryKey('name', newName));
     },
   });
 }
@@ -106,7 +121,7 @@ export function useCopyQualityGateMutation(sourceName: string) {
       return copyQualityGate({ sourceName, name: newName });
     },
     onSuccess: () => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
     },
   });
 }
@@ -119,7 +134,7 @@ export function useDeleteQualityGateMutation(name: string) {
       return deleteQualityGate({ name });
     },
     onSuccess: () => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
     },
   });
 }
@@ -154,8 +169,8 @@ export function useFixQualityGateMutation(gateName: string) {
       return Promise.all(promiseArr);
     },
     onSuccess: () => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
-      queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
+      queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
       addGlobalSuccessMessage(translate('quality_gates.conditions_updated'));
     },
   });
@@ -169,16 +184,19 @@ export function useCreateConditionMutation(gateName: string) {
       return createCondition({ ...condition, gateName });
     },
     onSuccess: (_, condition) => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
-      queryClient.setQueryData([QUALITY_GATE_KEY, gateName], (oldData?: QualityGate) => {
-        return oldData?.conditions
-          ? {
-              ...oldData,
-              conditions: [...oldData.conditions, condition],
-            }
-          : undefined;
-      });
-      queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
+      queryClient.setQueryData(
+        getQualityGateQueryKey('name', gateName),
+        (oldData?: QualityGate) => {
+          return oldData?.conditions
+            ? {
+                ...oldData,
+                conditions: [...oldData.conditions, condition],
+              }
+            : undefined;
+        },
+      );
+      queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
       addGlobalSuccessMessage(translate('quality_gates.condition_added'));
     },
   });
@@ -192,8 +210,8 @@ export function useUpdateConditionMutation(gateName: string) {
       return updateCondition(condition);
     },
     onSuccess: () => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
-      queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
+      queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
       addGlobalSuccessMessage(translate('quality_gates.condition_updated'));
     },
   });
@@ -209,8 +227,8 @@ export function useDeleteConditionMutation(gateName: string) {
       });
     },
     onSuccess: () => {
-      queryClient.invalidateQueries([QUALITY_GATES_KEY]);
-      queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+      queryClient.invalidateQueries(getQualityGateQueryKey());
+      queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
       addGlobalSuccessMessage(translate('quality_gates.condition_deleted'));
     },
   });
index bac8bcf99eea40987d2665521cf5719644e9a6e3..eb2e4f8bc03ce2fc73035b11108ea292b09bce70 100644 (file)
@@ -518,7 +518,12 @@ export enum CaycStatus {
   OverCompliant = 'over-compliant',
 }
 
-export interface QualityGate {
+export interface QualityGatePreview {
+  isDefault?: boolean;
+  name: string;
+}
+
+export interface QualityGate extends QualityGatePreview {
   actions?: {
     associateProjects?: boolean;
     copy?: boolean;
@@ -531,8 +536,6 @@ export interface QualityGate {
   conditions?: Condition[];
   isBuiltIn?: boolean;
   caycStatus?: CaycStatus;
-  isDefault?: boolean;
-  name: string;
 }
 
 export type RawQuery = Dict<any>;