From 5affe7f65f2d4d0e92864ae685299c70cec1a1d2 Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Thu, 22 Aug 2024 17:05:34 +0200 Subject: [PATCH] SONAR-22728 Fix BBT tests + bugs related to that --- .../main/js/apps/component-measures/utils.ts | 6 +- .../apps/overview/branches/BranchOverview.tsx | 1 - .../src/main/js/apps/projectDeletion/Form.tsx | 67 ++++++++------- .../src/main/js/queries/applications.ts | 15 +++- .../sonar-web/src/main/js/queries/measures.ts | 86 +++++++++++++++---- .../src/main/js/queries/portfolios.ts | 32 +++++++ .../sonar-web/src/main/js/queries/projects.ts | 14 ++- .../sonar-web/src/main/js/queries/settings.ts | 2 + .../org/sonarqube/ws/tester/UserTester.java | 4 + 9 files changed, 169 insertions(+), 58 deletions(-) create mode 100644 server/sonar-web/src/main/js/queries/portfolios.ts diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.ts b/server/sonar-web/src/main/js/apps/component-measures/utils.ts index 5e639f5fa11..e8961d51ac7 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts @@ -290,11 +290,7 @@ export function hasFullMeasures(branch?: BranchLike) { } export function getMeasuresPageMetricKeys(metrics: Dict, 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)); diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx index abe81a18c4f..b340d736035 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx @@ -126,7 +126,6 @@ export default function BranchOverview(props: Readonly) { ]) : BRANCH_OVERVIEW_METRICS : BRANCH_OVERVIEW_METRICS, - branchParameters: getBranchLikeQuery(branch), }); const getEnhancedConditions = ( diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx index 9de1ebc5f82..23df77720b9 100644 --- a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx +++ b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx @@ -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 { - handleDelete = async () => { - const { component } = this.props; +export function Form({ component, router }: Readonly) { + 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 { 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 ( - - {({ onClick }) => ( - - )} - - ); - } + return ( + + {({ onClick }) => ( + + )} + + ); } export default withRouter(Form); diff --git a/server/sonar-web/src/main/js/queries/applications.ts b/server/sonar-web/src/main/js/queries/applications.ts index 7d3a73afdf2..c08095b30e2 100644 --- a/server/sonar-web/src/main/js/queries/applications.ts +++ b/server/sonar-web/src/main/js/queries/applications.ts @@ -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); + }, + }); +} diff --git a/server/sonar-web/src/main/js/queries/measures.ts b/server/sonar-web/src/main/js/queries/measures.ts index 479370883af..dc34baa0637 100644 --- a/server/sonar-web/src/main/js/queries/measures.ts +++ b/server/sonar-web/src/main/js/queries/measures.ts @@ -18,7 +18,12 @@ * 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( - ['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( - ['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( - ['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( - ['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 index 00000000000..c8e4182011b --- /dev/null +++ b/server/sonar-web/src/main/js/queries/portfolios.ts @@ -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); + }, + }); +} diff --git a/server/sonar-web/src/main/js/queries/projects.ts b/server/sonar-web/src/main/js/queries/projects.ts index 39eab402621..38921f593ea 100644 --- a/server/sonar-web/src/main/js/queries/projects.ts +++ b/server/sonar-web/src/main/js/queries/projects.ts @@ -17,9 +17,11 @@ * 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); + }, + }); +} diff --git a/server/sonar-web/src/main/js/queries/settings.ts b/server/sonar-web/src/main/js/queries/settings.ts index 2648e452844..5deba9ca198 100644 --- a/server/sonar-web/src/main/js/queries/settings.ts +++ b/server/sonar-web/src/main/js/queries/settings.ts @@ -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')); }, }); diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java index a376b110811..350cb661a1a 100644 --- a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java +++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java @@ -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; } -- 2.39.5