]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22862 Refactor permission mapping API changes (#11630)
authorSarath Nair <91882341+sarath-nair-sonarsource@users.noreply.github.com>
Thu, 29 Aug 2024 07:28:09 +0000 (09:28 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 29 Aug 2024 20:02:47 +0000 (20:02 +0000)
12 files changed:
server/sonar-web/src/main/js/api/github-provisioning.ts
server/sonar-web/src/main/js/api/gitlab-provisioning.ts
server/sonar-web/src/main/js/api/mocks/GithubProvisioningServiceMock.ts
server/sonar-web/src/main/js/api/mocks/GitlabProvisioningServiceMock.ts
server/sonar-web/src/main/js/apps/settings/components/authentication/DevopsRolesMappingModal.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/GitHubAuthenticationTab.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/GitHubMappingModal.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/GitLabAuthenticationTab.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/GitLabMappingModal.tsx
server/sonar-web/src/main/js/queries/identity-provider/github.ts
server/sonar-web/src/main/js/queries/identity-provider/gitlab.ts
server/sonar-web/src/main/js/types/provisioning.ts

index eb9709da510df8b96615c6b832f2fdc6b743b049..1cb688d5755132f1e127b4edeff4f573715dddee 100644 (file)
@@ -21,7 +21,7 @@ import axios from 'axios';
 import { throwGlobalError } from '~sonar-aligned/helpers/error';
 import { getJSON } from '~sonar-aligned/helpers/request';
 import { post, postJSON } from '../helpers/request';
-import { GitHubConfigurationStatus, GitHubMapping, GithubStatus } from '../types/provisioning';
+import { DevopsRolesMapping, GitHubConfigurationStatus, GithubStatus } from '../types/provisioning';
 
 const GITHUB_PERMISSION_MAPPINGS = '/api/v2/dop-translation/github-permission-mappings';
 
@@ -39,22 +39,22 @@ export function syncNowGithubProvisioning(): Promise<void> {
 
 export function fetchGithubRolesMapping() {
   return axios
-    .get<{ githubPermissionsMappings: GitHubMapping[] }>(GITHUB_PERMISSION_MAPPINGS)
-    .then((data) => data.githubPermissionsMappings);
+    .get<{ permissionMappings: DevopsRolesMapping[] }>(GITHUB_PERMISSION_MAPPINGS)
+    .then((data) => data.permissionMappings);
 }
 
 export function updateGithubRolesMapping(
   role: string,
-  data: Partial<Pick<GitHubMapping, 'permissions'>>,
+  data: Partial<Pick<DevopsRolesMapping, 'permissions'>>,
 ) {
-  return axios.patch<GitHubMapping>(
+  return axios.patch<DevopsRolesMapping>(
     `${GITHUB_PERMISSION_MAPPINGS}/${encodeURIComponent(role)}`,
     data,
   );
 }
 
-export function addGithubRolesMapping(data: Omit<GitHubMapping, 'id'>) {
-  return axios.post<GitHubMapping>(GITHUB_PERMISSION_MAPPINGS, data);
+export function addGithubRolesMapping(data: Omit<DevopsRolesMapping, 'id'>) {
+  return axios.post<DevopsRolesMapping>(GITHUB_PERMISSION_MAPPINGS, data);
 }
 
 export function deleteGithubRolesMapping(role: string) {
index 59f10624eb0b2dc60b845b05bcd92a8fb08851df..e69a372d799277b4b7d63ab19331b4420adf1591 100644 (file)
@@ -19,9 +19,9 @@
  */
 import axios from 'axios';
 import {
+  DevopsRolesMapping,
   GitLabConfigurationCreateBody,
   GitLabConfigurationUpdateBody,
-  GitLabMapping,
   GitlabConfiguration,
   ProvisioningType,
 } from '../types/provisioning';
@@ -69,22 +69,22 @@ export function syncNowGitLabProvisioning(): Promise<void> {
 
 export function fetchGitlabRolesMapping() {
   return axios
-    .get<{ gitlabPermissionsMappings: GitLabMapping[] }>(GITLAB_PERMISSION_MAPPINGS)
-    .then((data) => data.gitlabPermissionsMappings);
+    .get<{ permissionMappings: DevopsRolesMapping[] }>(GITLAB_PERMISSION_MAPPINGS)
+    .then((data) => data.permissionMappings);
 }
 
 export function updateGitlabRolesMapping(
   role: string,
-  data: Partial<Pick<GitLabMapping, 'permissions'>>,
+  data: Partial<Pick<DevopsRolesMapping, 'permissions'>>,
 ) {
-  return axios.patch<GitLabMapping>(
+  return axios.patch<DevopsRolesMapping>(
     `${GITLAB_PERMISSION_MAPPINGS}/${encodeURIComponent(role)}`,
     data,
   );
 }
 
-export function addGitlabRolesMapping(data: Omit<GitLabMapping, 'id'>) {
-  return axios.post<GitLabMapping>(GITLAB_PERMISSION_MAPPINGS, data);
+export function addGitlabRolesMapping(data: Omit<DevopsRolesMapping, 'id'>) {
+  return axios.post<DevopsRolesMapping>(GITLAB_PERMISSION_MAPPINGS, data);
 }
 
 export function deleteGitlabRolesMapping(role: string) {
index cdb223417d60e62e74e0b0968a239bf2ad300f40..aa0c6b78d03559bc40c79e4d6729ec8cc5981ffb 100644 (file)
@@ -20,8 +20,8 @@
 import { cloneDeep } from 'lodash';
 import { mockTask } from '../../helpers/mocks/tasks';
 import {
+  DevopsRolesMapping,
   GitHubConfigurationStatus,
-  GitHubMapping,
   GitHubProvisioningStatus,
   ProvisioningType,
 } from '../../types/provisioning';
@@ -62,11 +62,11 @@ const defaultConfigurationStatus: GitHubConfigurationStatus = {
 
 const githubMappingMock = (
   id: string,
-  permissions: (keyof GitHubMapping['permissions'])[],
+  permissions: (keyof DevopsRolesMapping['permissions'])[],
   baseRole = false,
 ) => ({
   id,
-  githubRole: id,
+  role: id,
   baseRole,
   permissions: {
     user: permissions.includes('user'),
@@ -78,7 +78,7 @@ const githubMappingMock = (
   },
 });
 
-const defaultMapping: GitHubMapping[] = [
+const defaultMapping: DevopsRolesMapping[] = [
   githubMappingMock('read', ['user', 'codeViewer'], true),
   githubMappingMock(
     'write',
@@ -101,7 +101,7 @@ const defaultMapping: GitHubMapping[] = [
 export default class GithubProvisioningServiceMock {
   dopTranslationServiceMock?: DopTranslationServiceMock;
   githubConfigurationStatus: GitHubConfigurationStatus;
-  githubMapping: GitHubMapping[];
+  githubMapping: DevopsRolesMapping[];
   tasks: Task[];
 
   constructor(dopTranslationServiceMock?: DopTranslationServiceMock) {
@@ -185,12 +185,12 @@ export default class GithubProvisioningServiceMock {
     );
 
     return Promise.resolve(
-      this.githubMapping.find((mapping) => mapping.id === id) as GitHubMapping,
+      this.githubMapping.find((mapping) => mapping.id === id) as DevopsRolesMapping,
     );
   };
 
   handleAddGithubRolesMapping: typeof addGithubRolesMapping = (data) => {
-    const newRole = { ...data, id: data.githubRole };
+    const newRole = { ...data, id: data.role };
     this.githubMapping = [...this.githubMapping, newRole];
 
     return Promise.resolve(newRole);
@@ -201,7 +201,7 @@ export default class GithubProvisioningServiceMock {
     return Promise.resolve();
   };
 
-  addGitHubCustomRole = (id: string, permissions: (keyof GitHubMapping['permissions'])[]) => {
+  addGitHubCustomRole = (id: string, permissions: (keyof DevopsRolesMapping['permissions'])[]) => {
     this.githubMapping = [...this.githubMapping, githubMappingMock(id, permissions)];
   };
 
index e7b9529143b31c81489ed02edbf9ddc30d4684d9..ab66b53b0f7ad393c887f1699e91d5e0e5823ce7 100644 (file)
@@ -20,7 +20,7 @@
 import { cloneDeep, omit } from 'lodash';
 import { mockGitlabConfiguration } from '../../helpers/mocks/alm-integrations';
 import { mockPaging } from '../../helpers/testMocks';
-import { GitLabMapping, GitlabConfiguration } from '../../types/provisioning';
+import { DevopsRolesMapping, GitlabConfiguration } from '../../types/provisioning';
 import {
   addGitlabRolesMapping,
   createGitLabConfiguration,
@@ -41,11 +41,11 @@ const defaultGitlabConfiguration: GitlabConfiguration[] = [
 
 const gitlabMappingMock = (
   id: string,
-  permissions: (keyof GitLabMapping['permissions'])[],
+  permissions: (keyof DevopsRolesMapping['permissions'])[],
   baseRole = false,
 ) => ({
   id,
-  gitlabRole: id,
+  role: id,
   baseRole,
   permissions: {
     user: permissions.includes('user'),
@@ -57,7 +57,7 @@ const gitlabMappingMock = (
   },
 });
 
-const defaultMapping: GitLabMapping[] = [
+const defaultMapping: DevopsRolesMapping[] = [
   gitlabMappingMock('guest', ['user', 'codeViewer'], true),
   gitlabMappingMock('reporter', ['user', 'codeViewer'], true),
   gitlabMappingMock(
@@ -79,7 +79,7 @@ const defaultMapping: GitLabMapping[] = [
 
 export default class GitlabProvisioningServiceMock {
   gitlabConfigurations: GitlabConfiguration[];
-  gitlabMapping: GitLabMapping[];
+  gitlabMapping: DevopsRolesMapping[];
 
   constructor() {
     this.gitlabConfigurations = cloneDeep(defaultGitlabConfiguration);
@@ -143,12 +143,12 @@ export default class GitlabProvisioningServiceMock {
     );
 
     return Promise.resolve(
-      this.gitlabMapping.find((mapping) => mapping.id === id) as GitLabMapping,
+      this.gitlabMapping.find((mapping) => mapping.id === id) as DevopsRolesMapping,
     );
   };
 
   handleAddGitlabRolesMapping: typeof addGitlabRolesMapping = (data) => {
-    const newRole = { ...data, id: data.gitlabRole };
+    const newRole = { ...data, id: data.role };
     this.gitlabMapping = [...this.gitlabMapping, newRole];
 
     return Promise.resolve(newRole);
@@ -159,7 +159,7 @@ export default class GitlabProvisioningServiceMock {
     return Promise.resolve();
   };
 
-  addGitLabCustomRole = (id: string, permissions: (keyof GitLabMapping['permissions'])[]) => {
+  addGitLabCustomRole = (id: string, permissions: (keyof DevopsRolesMapping['permissions'])[]) => {
     this.gitlabMapping = [...this.gitlabMapping, gitlabMappingMock(id, permissions)];
   };
 
index 4fe480028e4060380091c39a97eec49ac9eb7ba6..4b70d1140d5a81284881d802f824b796546484d1 100644 (file)
@@ -42,25 +42,23 @@ import {
   isPermissionDefinitionGroup,
 } from '../../../../helpers/permissions';
 import { AlmKeys } from '../../../../types/alm-settings';
-import { GitHubMapping, GitLabMapping } from '../../../../types/provisioning';
-
-type RolesMapping = GitHubMapping[] | GitLabMapping[] | null;
+import { DevopsRolesMapping } from '../../../../types/provisioning';
 
 interface Props {
   isLoading: boolean;
-  mapping: RolesMapping;
+  mapping: DevopsRolesMapping[] | null;
   mappingFor: AlmKeys.GitHub | AlmKeys.GitLab;
   onClose: () => void;
-  roles?: RolesMapping;
-  setMapping: React.Dispatch<React.SetStateAction<RolesMapping>>;
+  roles?: DevopsRolesMapping[] | null;
+  setMapping: React.Dispatch<React.SetStateAction<DevopsRolesMapping[] | null>>;
 }
 
 interface PermissionCellProps extends Pick<Props, 'setMapping'> {
-  list?: GitHubMapping[] | GitLabMapping[];
-  mapping: GitHubMapping | GitLabMapping;
+  list?: DevopsRolesMapping[];
+  mapping: DevopsRolesMapping;
 }
 
-const DEFAULT_CUSTOM_ROLE_PERMISSIONS: GitHubMapping['permissions'] = {
+const DEFAULT_CUSTOM_ROLE_PERMISSIONS: DevopsRolesMapping['permissions'] = {
   user: true,
   codeViewer: false,
   issueAdmin: false,
@@ -71,30 +69,21 @@ const DEFAULT_CUSTOM_ROLE_PERMISSIONS: GitHubMapping['permissions'] = {
 
 function PermissionRow(props: Readonly<PermissionCellProps>) {
   const { list, mapping } = props;
-  const isGitHubMapping = 'githubRole' in mapping;
-  const role = isGitHubMapping ? mapping.githubRole : mapping.gitlabRole;
-  const isBaseRole = mapping.baseRole;
+  const { role, baseRole } = mapping;
 
   const setMapping = () => {
-    if (isGitHubMapping) {
-      return props.setMapping(
-        (list as GitHubMapping[])?.filter((r) => r.githubRole !== role) ?? null,
-      );
-    }
-    return props.setMapping(
-      (list as GitLabMapping[])?.filter((r) => r.gitlabRole !== role) ?? null,
-    );
+    return props.setMapping(list?.filter((r) => r.role !== role) ?? null);
   };
 
   return (
     <TableRowInteractive>
       <ContentCell scope="row" className="sw-whitespace-nowrap">
         <div className="sw-flex sw-max-w-[330px] sw-items-center">
-          <b className={isBaseRole ? 'sw-capitalize' : 'sw-truncate'} title={role}>
+          <b className={baseRole ? 'sw-capitalize' : 'sw-truncate'} title={role}>
             {role}
           </b>
 
-          {!isBaseRole && (
+          {!baseRole && (
             <DestructiveIcon
               className="sw-ml-1"
               aria-label={translateWithParameters(
@@ -114,11 +103,11 @@ function PermissionRow(props: Readonly<PermissionCellProps>) {
             checked={value}
             onCheck={(newValue) =>
               props.setMapping(
-                (list?.map((item) =>
+                list?.map((item) =>
                   item.id === mapping.id
                     ? { ...item, permissions: { ...item.permissions, [key]: newValue } }
                     : item,
-                ) ?? null) as RolesMapping,
+                ) ?? null,
               )
             }
           />
@@ -148,33 +137,17 @@ export function DevopsRolesMappingModal(props: Readonly<Props>) {
     e.preventDefault();
     const value = customRoleInput.trim();
     if (
-      mappingFor === AlmKeys.GitHub &&
-      !(list as GitHubMapping[])?.some((el) =>
-        el.baseRole ? el.githubRole.toLowerCase() === value.toLowerCase() : el.githubRole === value,
-      )
-    ) {
-      setMapping([
-        {
-          id: customRoleInput,
-          githubRole: customRoleInput,
-          permissions: { ...DEFAULT_CUSTOM_ROLE_PERMISSIONS },
-        },
-        ...((list as GitHubMapping[]) ?? []),
-      ]);
-      setCustomRoleInput('');
-    } else if (
-      mappingFor === AlmKeys.GitLab &&
-      !(list as GitLabMapping[])?.some((el) =>
-        el.baseRole ? el.gitlabRole.toLowerCase() === value.toLowerCase() : el.gitlabRole === value,
+      !list?.some((el) =>
+        el.baseRole ? el.role.toLowerCase() === value.toLowerCase() : el.role === value,
       )
     ) {
       setMapping([
         {
           id: customRoleInput,
-          gitlabRole: customRoleInput,
+          role: customRoleInput,
           permissions: { ...DEFAULT_CUSTOM_ROLE_PERMISSIONS },
         },
-        ...((list as GitLabMapping[]) ?? []),
+        ...(list ?? []),
       ]);
       setCustomRoleInput('');
     } else {
index 37ec3ec70e002954a9f9469d07ff2f5b05e8ece6..057bebd138be19b8c98a05c9241d3ffdebbcfca3 100644 (file)
@@ -40,7 +40,7 @@ import {
 } from '../../../../queries/identity-provider/github';
 import { GitHubConfigurationPayload } from '../../../../types/dop-translation';
 import { Feature } from '../../../../types/features';
-import { GitHubMapping, ProvisioningType } from '../../../../types/provisioning';
+import { DevopsRolesMapping, ProvisioningType } from '../../../../types/provisioning';
 import { Provider } from '../../../../types/types';
 import AuthenticationFormField from './AuthenticationFormField';
 import AutoProvisioningConsent from './AutoProvisionningConsent';
@@ -63,7 +63,7 @@ export default function GitHubAuthenticationTab() {
   const [tokenKey, setTokenKey] = React.useState<number>(0);
   const [isConfirmProvisioningModalOpen, setIsConfirmProvisioningModalOpen] = React.useState(false);
   const [isMappingModalOpen, setIsMappingModalOpen] = useState(false);
-  const [rolesMapping, setRolesMapping] = useState<GitHubMapping[] | null>(null);
+  const [rolesMapping, setRolesMapping] = useState<DevopsRolesMapping[] | null>(null);
 
   const hasGithubProvisioning = useContext(AvailableFeaturesContext).includes(
     Feature.GithubProvisioning,
index 8b4a26362c9e3023fd523da06f71590c52838bcb..cfde5ce3396578a5d872a6880094868b072349a0 100644 (file)
 import * as React from 'react';
 import { useGithubRolesMappingQuery } from '../../../../queries/identity-provider/github';
 import { AlmKeys } from '../../../../types/alm-settings';
-import { GitHubMapping } from '../../../../types/provisioning';
+import { DevopsRolesMapping } from '../../../../types/provisioning';
 import { DevopsRolesMappingModal } from './DevopsRolesMappingModal';
 
 interface Props {
-  mapping: GitHubMapping[] | null;
+  mapping: DevopsRolesMapping[] | null;
   onClose: () => void;
-  setMapping: React.Dispatch<React.SetStateAction<GitHubMapping[] | null>>;
+  setMapping: React.Dispatch<React.SetStateAction<DevopsRolesMapping[] | null>>;
 }
 
 export default function GitHubMappingModal(props: Readonly<Props>) {
index 67dcab52af5059bdd8c075a843d73ffa43c1d62f..55e696a0a6f56db73440f403c3b2b40e3ea85f59 100644 (file)
@@ -37,8 +37,8 @@ import {
 } from '../../../../queries/identity-provider/gitlab';
 import { Feature } from '../../../../types/features';
 import {
+  DevopsRolesMapping,
   GitLabConfigurationUpdateBody,
-  GitLabMapping,
   ProvisioningType,
 } from '../../../../types/provisioning';
 import { DefinitionV2, SettingType } from '../../../../types/settings';
@@ -95,7 +95,7 @@ export default function GitLabAuthenticationTab() {
   const [tokenKey, setTokenKey] = useState<number>(0);
   const [showConfirmProvisioningModal, setShowConfirmProvisioningModal] = useState(false);
   const [isMappingModalOpen, setIsMappingModalOpen] = useState(false);
-  const [rolesMapping, setRolesMapping] = useState<GitLabMapping[] | null>(null);
+  const [rolesMapping, setRolesMapping] = useState<DevopsRolesMapping[] | null>(null);
   const { mutateAsync: updateMapping } = useGitlabRolesMappingMutation();
 
   const hasGitlabProvisioningFeature = useContext(AvailableFeaturesContext).includes(
index b5b8c2a60f474c394ef791920b6c7b37bbdd2218..e74bc66920a8437dc9f80723811c3d810e2d9156 100644 (file)
 import * as React from 'react';
 import { useGitlabRolesMappingQuery } from '../../../../queries/identity-provider/gitlab';
 import { AlmKeys } from '../../../../types/alm-settings';
-import { GitLabMapping } from '../../../../types/provisioning';
+import { DevopsRolesMapping } from '../../../../types/provisioning';
 import { DevopsRolesMappingModal } from './DevopsRolesMappingModal';
 
 interface Props {
-  mapping: GitLabMapping[] | null;
+  mapping: DevopsRolesMapping[] | null;
   onClose: () => void;
-  setMapping: React.Dispatch<React.SetStateAction<GitLabMapping[] | null>>;
+  setMapping: React.Dispatch<React.SetStateAction<DevopsRolesMapping[] | null>>;
 }
 
 export default function GitLabMappingModal(props: Readonly<Props>) {
index 41407db3f7a18c93ee798494ff3111f137bde103..04c03109f11544e64e00f75d252cd5a97a37c7a8 100644 (file)
@@ -17,7 +17,7 @@
  * 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, useQuery, useQueryClient } from '@tanstack/react-query';
+import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import { addGlobalSuccessMessage } from 'design-system';
 import { isEqual, keyBy, partition, pick, unionBy } from 'lodash';
 import { useContext } from 'react';
@@ -34,9 +34,8 @@ import { AvailableFeaturesContext } from '../../app/components/available-feature
 import { translate } from '../../helpers/l10n';
 import { mapReactQueryResult } from '../../helpers/react-query';
 import { Feature } from '../../types/features';
-import { GitHubMapping } from '../../types/provisioning';
-
-const MAPPING_STALE_TIME = 60_000;
+import { DevopsRolesMapping } from '../../types/provisioning';
+import { StaleTime, createQueryHook } from '../common';
 
 export const useCheckGitHubConfigQuery = (githubEnabled: boolean) => {
   return useQuery({
@@ -89,27 +88,30 @@ export function useSyncWithGitHubNow() {
 // Order is reversed to put custom roles at the end (their index is -1)
 const defaultRoleOrder = ['admin', 'maintain', 'write', 'triage', 'read'];
 
-export function useGithubRolesMappingQuery() {
-  return useQuery({
+function sortGithubRoles(data: DevopsRolesMapping[]) {
+  return [...data].sort((a, b) => {
+    if (defaultRoleOrder.includes(a.id) || defaultRoleOrder.includes(b.id)) {
+      return defaultRoleOrder.indexOf(b.id) - defaultRoleOrder.indexOf(a.id);
+    }
+    return a.role.localeCompare(b.role);
+  });
+}
+
+export const useGithubRolesMappingQuery = createQueryHook(() => {
+  return queryOptions({
     queryKey: ['identity_provider', 'github_mapping'],
     queryFn: fetchGithubRolesMapping,
-    staleTime: MAPPING_STALE_TIME,
-    select: (data) =>
-      [...data].sort((a, b) => {
-        if (defaultRoleOrder.includes(a.id) || defaultRoleOrder.includes(b.id)) {
-          return defaultRoleOrder.indexOf(b.id) - defaultRoleOrder.indexOf(a.id);
-        }
-        return a.githubRole.localeCompare(b.githubRole);
-      }),
+    staleTime: StaleTime.LONG,
+    select: sortGithubRoles, // uses a stable function reference
   });
-}
+});
 
 export function useGithubRolesMappingMutation() {
   const client = useQueryClient();
   const queryKey = ['identity_provider', 'github_mapping'];
   return useMutation({
-    mutationFn: async (mapping: GitHubMapping[]) => {
-      const state = keyBy(client.getQueryData<GitHubMapping[]>(queryKey), (m) => m.id);
+    mutationFn: async (mapping: DevopsRolesMapping[]) => {
+      const state = keyBy(client.getQueryData<DevopsRolesMapping[]>(queryKey), (m) => m.id);
 
       const [maybeChangedRoles, newRoles] = partition(mapping, (m) => state[m.id]);
       const changedRoles = maybeChangedRoles.filter((item) => !isEqual(item, state[item.id]));
@@ -130,7 +132,7 @@ export function useGithubRolesMappingMutation() {
       };
     },
     onSuccess: ({ addedOrChanged, deleted }) => {
-      const state = client.getQueryData<GitHubMapping[]>(queryKey);
+      const state = client.getQueryData<DevopsRolesMapping[]>(queryKey);
       if (state) {
         const newData = unionBy(
           addedOrChanged,
index fb1bb25436a9b39c34b820674039fcd716fc7b50..a88cb9612b3c176f70eba7cf681dc0686dc3c298 100644 (file)
@@ -17,7 +17,7 @@
  * 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, useQuery, useQueryClient } from '@tanstack/react-query';
+import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import { addGlobalSuccessMessage } from 'design-system';
 import { isEqual, keyBy, partition, pick, unionBy } from 'lodash';
 import { getActivity } from '../../api/ce';
@@ -34,10 +34,9 @@ import {
 } from '../../api/gitlab-provisioning';
 import { translate } from '../../helpers/l10n';
 import { mapReactQueryResult } from '../../helpers/react-query';
-import { AlmSyncStatus, GitLabMapping, ProvisioningType } from '../../types/provisioning';
+import { AlmSyncStatus, DevopsRolesMapping, ProvisioningType } from '../../types/provisioning';
 import { TaskStatuses, TaskTypes } from '../../types/tasks';
-
-const MAPPING_STALE_TIME = 60_000;
+import { createQueryHook, StaleTime } from '../common';
 
 export function useGitLabConfigurationsQuery() {
   return useQuery({
@@ -200,27 +199,30 @@ export function useSyncWithGitLabNow() {
 // Order is reversed to put custom roles at the end (their index is -1)
 const defaultRoleOrder = ['owner', 'maintainer', 'developer', 'reporter', 'guest'];
 
-export function useGitlabRolesMappingQuery() {
-  return useQuery({
+function sortGitlabRoles(data: DevopsRolesMapping[]) {
+  return [...data].sort((a, b) => {
+    if (defaultRoleOrder.includes(a.id) || defaultRoleOrder.includes(b.id)) {
+      return defaultRoleOrder.indexOf(b.id) - defaultRoleOrder.indexOf(a.id);
+    }
+    return a.role.localeCompare(b.role);
+  });
+}
+
+export const useGitlabRolesMappingQuery = createQueryHook(() => {
+  return queryOptions({
     queryKey: ['identity_provider', 'gitlab_mapping'],
     queryFn: fetchGitlabRolesMapping,
-    staleTime: MAPPING_STALE_TIME,
-    select: (data) =>
-      [...data].sort((a, b) => {
-        if (defaultRoleOrder.includes(a.id) || defaultRoleOrder.includes(b.id)) {
-          return defaultRoleOrder.indexOf(b.id) - defaultRoleOrder.indexOf(a.id);
-        }
-        return a.gitlabRole.localeCompare(b.gitlabRole);
-      }),
+    staleTime: StaleTime.LONG,
+    select: sortGitlabRoles,
   });
-}
+});
 
 export function useGitlabRolesMappingMutation() {
   const client = useQueryClient();
   const queryKey = ['identity_provider', 'gitlab_mapping'];
   return useMutation({
-    mutationFn: async (mapping: GitLabMapping[]) => {
-      const state = keyBy(client.getQueryData<GitLabMapping[]>(queryKey), (m) => m.id);
+    mutationFn: async (mapping: DevopsRolesMapping[]) => {
+      const state = keyBy(client.getQueryData<DevopsRolesMapping[]>(queryKey), (m) => m.id);
 
       const [maybeChangedRoles, newRoles] = partition(mapping, (m) => state[m.id]);
       const changedRoles = maybeChangedRoles.filter((item) => !isEqual(item, state[item.id]));
@@ -241,7 +243,7 @@ export function useGitlabRolesMappingMutation() {
       };
     },
     onSuccess: ({ addedOrChanged, deleted }) => {
-      const state = client.getQueryData<GitLabMapping[]>(queryKey);
+      const state = client.getQueryData<DevopsRolesMapping[]>(queryKey);
       if (state) {
         const newData = unionBy(
           addedOrChanged,
index ac47a51e196e1690d5f9c6c2df5064dd95ec37eb..c15fcd5afa0cb0f7efa9b4754140b101550eda87 100644 (file)
@@ -78,7 +78,7 @@ export interface GitHubConfigurationStatus {
   }[];
 }
 
-interface DevopsRolesMapping {
+export interface DevopsRolesMapping {
   readonly baseRole?: boolean;
   readonly id: string;
   permissions: {
@@ -89,14 +89,7 @@ interface DevopsRolesMapping {
     securityHotspotAdmin: boolean;
     user: boolean;
   };
-}
-
-export interface GitHubMapping extends DevopsRolesMapping {
-  readonly githubRole: string;
-}
-
-export interface GitLabMapping extends DevopsRolesMapping {
-  readonly gitlabRole: string;
+  readonly role: string;
 }
 
 export interface GitLabConfigurationCreateBody