]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22728 Fix BBT tests + bugs related to that
authorViktor Vorona <viktor.vorona@sonarsource.com>
Thu, 22 Aug 2024 15:05:34 +0000 (17:05 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Aug 2024 20:03:08 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/component-measures/utils.ts
server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx
server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
server/sonar-web/src/main/js/queries/applications.ts
server/sonar-web/src/main/js/queries/measures.ts
server/sonar-web/src/main/js/queries/portfolios.ts [new file with mode: 0644]
server/sonar-web/src/main/js/queries/projects.ts
server/sonar-web/src/main/js/queries/settings.ts
sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java

index 5e639f5fa1109b488fd71206a43dd2c3ac0546c3..e8961d51ac76d3aec64664ffbe1b869ff215dfca 100644 (file)
@@ -290,11 +290,7 @@ export function hasFullMeasures(branch?: BranchLike) {
 }
 
 export function getMeasuresPageMetricKeys(metrics: Dict<Metric>, branch?: BranchLike) {
-  // ToDo rollback once new metrics are available
-  const metricKeys = [
-    ...getDisplayMetrics(Object.values(metrics)).map((metric) => metric.key),
-    ...SOFTWARE_QUALITY_RATING_METRICS,
-  ];
+  const metricKeys = getDisplayMetrics(Object.values(metrics)).map((metric) => metric.key);
 
   if (isPullRequest(branch)) {
     return metricKeys.filter((key) => isDiffMetric(key));
index abe81a18c4f28ed359ae0542c113875d0846623b..b340d73603516a606b7fadc01528e8822ea67750 100644 (file)
@@ -126,7 +126,6 @@ export default function BranchOverview(props: Readonly<Props>) {
             ])
           : BRANCH_OVERVIEW_METRICS
         : BRANCH_OVERVIEW_METRICS,
-    branchParameters: getBranchLikeQuery(branch),
   });
 
   const getEnhancedConditions = (
index 9de1ebc5f824fa99a2095c525e159b6d8b6a5b02..23df77720b97bac44ac7baa38eb6be37ea076599 100644 (file)
@@ -24,10 +24,11 @@ import * as React from 'react';
 import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
 import { isPortfolioLike } from '~sonar-aligned/helpers/component';
 import { Router } from '~sonar-aligned/types/router';
-import { deleteApplication } from '../../api/application';
-import { deletePortfolio, deleteProject } from '../../api/project-management';
 import ConfirmButton from '../../components/controls/ConfirmButton';
 import { translate, translateWithParameters } from '../../helpers/l10n';
+import { useDeleteApplicationMutation } from '../../queries/applications';
+import { useDeletePortfolioMutation } from '../../queries/portfolios';
+import { useDeleteProjectMutation } from '../../queries/projects';
 import { isApplication } from '../../types/component';
 import { Component } from '../../types/types';
 
@@ -36,9 +37,12 @@ interface Props {
   router: Router;
 }
 
-export class Form extends React.PureComponent<Props> {
-  handleDelete = async () => {
-    const { component } = this.props;
+export function Form({ component, router }: Readonly<Props>) {
+  const { mutate: deleteProject } = useDeleteProjectMutation();
+  const { mutate: deleteApplication } = useDeleteApplicationMutation();
+  const { mutate: deletePortfolio } = useDeletePortfolioMutation();
+
+  const handleDelete = () => {
     let deleteMethod = deleteProject;
     let redirectTo = '/';
 
@@ -49,36 +53,35 @@ export class Form extends React.PureComponent<Props> {
       deleteMethod = deleteApplication;
     }
 
-    await deleteMethod(component.key);
-
-    addGlobalSuccessMessage(
-      translateWithParameters('project_deletion.resource_deleted', component.name),
-    );
+    deleteMethod(component.key, {
+      onSuccess: () => {
+        addGlobalSuccessMessage(
+          translateWithParameters('project_deletion.resource_deleted', component.name),
+        );
 
-    this.props.router.replace(redirectTo);
+        router.replace(redirectTo);
+      },
+    });
   };
 
-  render() {
-    const { component } = this.props;
-    return (
-      <ConfirmButton
-        confirmButtonText={translate('delete')}
-        isDestructive
-        modalBody={translateWithParameters(
-          'project_deletion.delete_resource_confirmation',
-          component.name,
-        )}
-        modalHeader={translate('qualifier.delete', component.qualifier)}
-        onConfirm={this.handleDelete}
-      >
-        {({ onClick }) => (
-          <Button id="delete-project" onClick={onClick} variety={ButtonVariety.Danger}>
-            {translate('delete')}
-          </Button>
-        )}
-      </ConfirmButton>
-    );
-  }
+  return (
+    <ConfirmButton
+      confirmButtonText={translate('delete')}
+      isDestructive
+      modalBody={translateWithParameters(
+        'project_deletion.delete_resource_confirmation',
+        component.name,
+      )}
+      modalHeader={translate('qualifier.delete', component.qualifier)}
+      onConfirm={handleDelete}
+    >
+      {({ onClick }) => (
+        <Button id="delete-project" onClick={onClick} variety={ButtonVariety.Danger}>
+          {translate('delete')}
+        </Button>
+      )}
+    </ConfirmButton>
+  );
 }
 
 export default withRouter(Form);
index 7d3a73afdf2cf40c529647ae7df72baaccf121d3..c08095b30e2d997aa42f522aadb6cf2aa7dbcb3b 100644 (file)
@@ -18,8 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { useQuery } from '@tanstack/react-query';
-import { getApplicationLeak } from '../api/application';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { deleteApplication, getApplicationLeak } from '../api/application';
+import { invalidateMeasuresByComponentKey } from './measures';
 
 export default function useApplicationLeakQuery(application: string, enabled = true) {
   return useQuery({
@@ -28,3 +29,13 @@ export default function useApplicationLeakQuery(application: string, enabled = t
     enabled,
   });
 }
+
+export function useDeleteApplicationMutation() {
+  const queryClient = useQueryClient();
+  return useMutation({
+    mutationFn: (key: string) => deleteApplication(key),
+    onSuccess: (_, key) => {
+      invalidateMeasuresByComponentKey(key, queryClient);
+    },
+  });
+}
index 479370883af8ced8d934e617fa4e8ffa0297fa06..dc34baa06371b9e8eb7a9f637421a71593f18f05 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { infiniteQueryOptions, queryOptions, useQueryClient } from '@tanstack/react-query';
+import {
+  infiniteQueryOptions,
+  QueryClient,
+  queryOptions,
+  useQueryClient,
+} from '@tanstack/react-query';
 import { groupBy, isUndefined, omitBy } from 'lodash';
 import { BranchParameters } from '~sonar-aligned/types/branch-like';
 import { getComponentTree } from '../api/components';
@@ -32,7 +37,29 @@ import { getNextPageParam, getPreviousPageParam } from '../helpers/react-query';
 import { getBranchLikeQuery } from '../sonar-aligned/helpers/branch-like';
 import { BranchLike } from '../types/branch-like';
 import { Measure } from '../types/types';
-import { createInfiniteQueryHook, createQueryHook, StaleTime } from './common';
+import { createInfiniteQueryHook, createQueryHook } from './common';
+
+export const invalidateMeasuresByComponentKey = (
+  componentKey: string,
+  queryClient: QueryClient,
+) => {
+  queryClient.invalidateQueries({ queryKey: ['measures', 'history', componentKey] });
+  queryClient.invalidateQueries({ queryKey: ['measures', 'component', componentKey] });
+  queryClient.invalidateQueries({ queryKey: ['measures', 'details', componentKey] });
+  queryClient.invalidateQueries({ queryKey: ['measures', 'list', componentKey] });
+  queryClient.invalidateQueries({
+    predicate: (query) =>
+      query.queryKey[0] === 'measures' &&
+      query.queryKey[1] === 'list' &&
+      query.queryKey[2] === 'projects' &&
+      Array.isArray(query.queryKey[3]) &&
+      query.queryKey[3].includes(componentKey),
+  });
+};
+
+export const invalidateAllMeasures = (queryClient: QueryClient) => {
+  queryClient.invalidateQueries({ queryKey: ['measures'] });
+};
 
 export const useAllMeasuresHistoryQuery = createQueryHook(
   ({
@@ -71,7 +98,14 @@ export const useMeasuresComponentQuery = createQueryHook(
     const branchLikeQuery = getBranchLikeQuery(branchLike);
 
     return queryOptions({
-      queryKey: ['measures', 'component', componentKey, 'branchLike', branchLikeQuery, metricKeys],
+      queryKey: [
+        'measures',
+        'component',
+        componentKey,
+        'branchLike',
+        { ...branchLikeQuery },
+        metricKeys,
+      ],
       queryFn: async () => {
         const data = await getMeasuresWithPeriodAndMetrics(
           componentKey,
@@ -82,7 +116,7 @@ export const useMeasuresComponentQuery = createQueryHook(
           const measure =
             data.component.measures?.find((measure) => measure.metric === metricKey) ?? null;
           queryClient.setQueryData<Measure | null>(
-            ['measures', 'details', componentKey, 'branchLike', branchLikeQuery, metricKey],
+            ['measures', 'details', componentKey, 'branchLike', { ...branchLikeQuery }, metricKey],
             measure,
           );
         });
@@ -115,7 +149,7 @@ export const useComponentTreeQuery = createInfiniteQueryHook(
 
     const queryClient = useQueryClient();
     return infiniteQueryOptions({
-      queryKey: ['component', component, 'tree', strategy, { metrics, additionalData }],
+      queryKey: ['measures', 'component', component, 'tree', strategy, { metrics, additionalData }],
       queryFn: async ({ pageParam }) => {
         const result = await getComponentTree(strategy, component, metrics, {
           ...additionalData,
@@ -136,7 +170,7 @@ export const useComponentTreeQuery = createInfiniteQueryHook(
                 'details',
                 result.baseComponent.key,
                 'branchLike',
-                branchLikeQuery,
+                { ...branchLikeQuery },
                 metricKey,
               ],
               measure,
@@ -153,7 +187,14 @@ export const useComponentTreeQuery = createInfiniteQueryHook(
           metrics?.forEach((metricKey) => {
             const measure = measuresMapByMetricKeyForChildComponent[metricKey]?.[0] ?? null;
             queryClient.setQueryData<Measure>(
-              ['measures', 'details', childComponent.key, 'branchLike', branchLikeQuery, metricKey],
+              [
+                'measures',
+                'details',
+                childComponent.key,
+                'branchLike',
+                { ...branchLikeQuery },
+                metricKey,
+              ],
               measure,
             );
           });
@@ -199,23 +240,21 @@ export const useMeasuresAndLeakQuery = createQueryHook(
     componentKey,
     metricKeys,
     branchLike,
-    branchParameters,
   }: {
     branchLike?: BranchLike;
-    branchParameters?: BranchParameters;
     componentKey: string;
     metricKeys: string[];
   }) => {
     const queryClient = useQueryClient();
+    const branchParameters = getBranchLikeQuery(branchLike);
     return queryOptions({
       queryKey: [
         'measures',
         'details',
-        'component',
         componentKey,
+        'branchLike',
+        { ...branchParameters },
         metricKeys,
-        branchLike,
-        branchParameters,
       ],
       queryFn: async () => {
         const { component, metrics, period } = await getMeasuresWithPeriodAndMetrics(
@@ -227,7 +266,7 @@ export const useMeasuresAndLeakQuery = createQueryHook(
         metricKeys.forEach((metricKey) => {
           const measure = measuresMapByMetricKey[metricKey]?.[0] ?? null;
           queryClient.setQueryData<Measure>(
-            ['measures', 'details', componentKey, 'branchLike', branchLike, metricKey],
+            ['measures', 'details', componentKey, 'branchLike', { ...branchParameters }, metricKey],
             measure,
           );
         });
@@ -250,7 +289,14 @@ export const useMeasureQuery = createQueryHook(
     const branchLikeQuery = getBranchLikeQuery(branchLike);
 
     return queryOptions({
-      queryKey: ['measures', 'details', componentKey, 'branchLike', branchLikeQuery, metricKey],
+      queryKey: [
+        'measures',
+        'details',
+        componentKey,
+        'branchLike',
+        { ...branchLikeQuery },
+        metricKey,
+      ],
       queryFn: () =>
         getMeasures({ component: componentKey, metricKeys: metricKey }).then(
           (measures) => measures[0] ?? null,
@@ -274,7 +320,14 @@ export const useMeasuresQuery = createQueryHook(
     const branchLikeQuery = getBranchLikeQuery(branchLike);
 
     return queryOptions({
-      queryKey: ['measures', 'list', componentKey, 'branchLike', branchLikeQuery, metricKeys],
+      queryKey: [
+        'measures',
+        'list',
+        componentKey,
+        'branchLike',
+        { ...branchLikeQuery },
+        metricKeys,
+      ],
       queryFn: async () => {
         const measures = await getMeasures({
           component: componentKey,
@@ -285,13 +338,12 @@ export const useMeasuresQuery = createQueryHook(
         metricKeys.split(',').forEach((metricKey) => {
           const measure = measuresMapByMetricKey[metricKey]?.[0] ?? null;
           queryClient.setQueryData<Measure>(
-            ['measures', 'details', componentKey, 'branchLike', branchLike ?? {}, metricKey],
+            ['measures', 'details', componentKey, 'branchLike', { ...branchLikeQuery }, metricKey],
             measure,
           );
         });
         return measures;
       },
-      staleTime: StaleTime.LONG,
     });
   },
 );
diff --git a/server/sonar-web/src/main/js/queries/portfolios.ts b/server/sonar-web/src/main/js/queries/portfolios.ts
new file mode 100644 (file)
index 0000000..c8e4182
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 { useMutation, useQueryClient } from '@tanstack/react-query';
+import { deletePortfolio } from '../api/project-management';
+import { invalidateMeasuresByComponentKey } from './measures';
+
+export function useDeletePortfolioMutation() {
+  const queryClient = useQueryClient();
+  return useMutation({
+    mutationFn: (key: string) => deletePortfolio(key),
+    onSuccess: (_, key) => {
+      invalidateMeasuresByComponentKey(key, queryClient);
+    },
+  });
+}
index 39eab402621fbbbfce12a205559be0e23fd254d3..38921f593ea9114741c64d25ed478865fd266585 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { queryOptions } from '@tanstack/react-query';
+import { queryOptions, useMutation, useQueryClient } from '@tanstack/react-query';
 import { searchProjects } from '../api/components';
+import { deleteProject } from '../api/project-management';
 import { createQueryHook } from './common';
+import { invalidateMeasuresByComponentKey } from './measures';
 
 export const useProjectQuery = createQueryHook((key: string) => {
   return queryOptions({
@@ -27,3 +29,13 @@ export const useProjectQuery = createQueryHook((key: string) => {
     queryFn: ({ queryKey: [, key] }) => searchProjects({ filter: `query=${key}` }),
   });
 });
+
+export function useDeleteProjectMutation() {
+  const queryClient = useQueryClient();
+  return useMutation({
+    mutationFn: (key: string) => deleteProject(key),
+    onSuccess: (_, key) => {
+      invalidateMeasuresByComponentKey(key, queryClient);
+    },
+  });
+}
index 2648e452844d98bae5d05c77432ba8b954449833..5deba9ca1988d344bb640e3e57f25c3bd75eb72f 100644 (file)
@@ -23,6 +23,7 @@ import { getValue, getValues, resetSettingValue, setSettingValue } from '../api/
 import { translate } from '../helpers/l10n';
 import { ExtendedSettingDefinition, SettingsKey } from '../types/settings';
 import { createQueryHook } from './common';
+import { invalidateAllMeasures } from './measures';
 
 type SettingValue = string | boolean | string[];
 
@@ -125,6 +126,7 @@ export function useSaveValueMutation() {
     onSuccess: (_, { definition }) => {
       queryClient.invalidateQueries({ queryKey: ['settings', 'details', definition.key] });
       queryClient.invalidateQueries({ queryKey: ['settings', 'values'] });
+      invalidateAllMeasures(queryClient);
       addGlobalSuccessMessage(translate('settings.authentication.form.settings.save_success'));
     },
   });
index a376b1108117e03e99bde654609e55cc7069b32c..350cb661a1a9f9f3eae6e8acea77d10c7d30c6b6 100644 (file)
@@ -122,6 +122,10 @@ public class UserTester {
       new org.sonarqube.ws.client.permissions.AddUserRequest()
         .setLogin(u.getLogin())
         .setPermission("portfoliocreator"));
+    session.wsClient().permissions().addUser(
+      new org.sonarqube.ws.client.permissions.AddUserRequest()
+        .setLogin(u.getLogin())
+        .setPermission("admin"));
     return u;
   }