]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19967 Use POST api/v2/users on the FE
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>
Mon, 31 Jul 2023 09:27:05 +0000 (11:27 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 31 Jul 2023 20:03:32 +0000 (20:03 +0000)
server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts
server/sonar-web/src/main/js/api/users.ts
server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx
server/sonar-web/src/main/js/helpers/request.ts
server/sonar-web/src/main/js/queries/users.ts

index decdb9c4d5385c9d5bbd6afed0aa580a1db8d85b..ad4cf06f83104087574d782fbbb86d2a7901e738 100644 (file)
@@ -28,11 +28,11 @@ import { addUserToGroup, removeUserFromGroup } from '../user_groups';
 import {
   UserGroup,
   changePassword,
-  createUser,
   deleteUser,
   getIdentityProviders,
   getUserGroups,
   getUsers,
+  postUser,
   updateUser,
 } from '../users';
 
@@ -125,7 +125,7 @@ export default class UsersServiceMock {
     jest.mocked(getSystemInfo).mockImplementation(this.handleGetSystemInfo);
     jest.mocked(getIdentityProviders).mockImplementation(this.handleGetIdentityProviders);
     jest.mocked(getUsers).mockImplementation((p) => this.handleGetUsers(p));
-    jest.mocked(createUser).mockImplementation(this.handleCreateUser);
+    jest.mocked(postUser).mockImplementation(this.handlePostUser);
     jest.mocked(updateUser).mockImplementation(this.handleUpdateUser);
     jest.mocked(getUserGroups).mockImplementation(this.handleGetUserGroups);
     jest.mocked(addUserToGroup).mockImplementation(this.handleAddUserToGroup);
@@ -248,19 +248,19 @@ export default class UsersServiceMock {
     });
   };
 
-  handleCreateUser = (data: {
+  handlePostUser = (data: {
     email?: string;
     local?: boolean;
     login: string;
     name: string;
     password?: string;
-    scmAccount: string[];
+    scmAccounts: string[];
   }) => {
-    const { email, local, login, name, scmAccount } = data;
-    if (scmAccount.some((a) => isEmpty(a.trim()))) {
+    const { email, local, login, name, scmAccounts } = data;
+    if (scmAccounts.some((a) => isEmpty(a.trim()))) {
       return Promise.reject({
         status: 400,
-        json: () => Promise.resolve({ errors: [{ msg: 'Error: Empty SCM' }] }),
+        json: () => Promise.resolve({ message: 'Error: Empty SCM' }),
       });
     }
     const newUser = mockRestUser({
@@ -268,7 +268,7 @@ export default class UsersServiceMock {
       local,
       login,
       name,
-      scmAccounts: scmAccount,
+      scmAccounts,
     });
     this.users.push(newUser);
     return this.reply(undefined);
@@ -373,7 +373,7 @@ export default class UsersServiceMock {
     const index = this.users.findIndex((u) => u.login === data.login);
     const user = this.users.splice(index, 1)[0];
     user.active = false;
-    return this.reply({ user });
+    return this.reply(undefined);
   };
 
   reset = () => {
index 68c37e2d869d2b552ed34349f3155a8003672e17..73812ab046ffa6d868bc126b2d55e147c56f1ef2 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { throwGlobalError } from '../helpers/error';
-import { deleteJSON, getJSON, HttpStatus, parseJSON, post, postJSON } from '../helpers/request';
+import {
+  deleteJSON,
+  getJSON,
+  HttpStatus,
+  parseJSON,
+  post,
+  postJSON,
+  postJSONBody,
+} from '../helpers/request';
 import { IdentityProvider, Paging } from '../types/types';
 import {
   ChangePasswordResults,
@@ -26,7 +34,6 @@ import {
   HomePage,
   NoticeType,
   RestUserBase,
-  RestUserDetailed,
   User,
 } from '../types/users';
 
@@ -89,15 +96,14 @@ export function getUsers<T extends RestUserBase>(data: {
   return getJSON('/api/v2/users', data).catch(throwGlobalError);
 }
 
-export function createUser(data: {
+export function postUser(data: {
   email?: string;
-  local?: boolean;
   login: string;
   name: string;
   password?: string;
-  scmAccount: string[];
+  scmAccounts: string[];
 }): Promise<void | Response> {
-  return post('/api/users/create', data);
+  return postJSONBody('/api/v2/users', data);
 }
 
 export function updateUser(data: {
@@ -118,7 +124,7 @@ export function deleteUser({
 }: {
   login: string;
   anonymize?: boolean;
-}): Promise<{ user: RestUserDetailed }> {
+}): Promise<void | Response> {
   return deleteJSON(`/api/v2/users/${login}`, { anonymize }).catch(throwGlobalError);
 }
 
index 664518cad3dd92196c1f51bb8931802f7aea4e99..111f4561a85ababfe9ebe5f12df882dccf9287ce 100644 (file)
@@ -24,7 +24,7 @@ import { CurrentUserContext } from '../../../app/components/current-user/Current
 import Avatar from '../../../components/ui/Avatar';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { Issue } from '../../../types/types';
-import { RestUser, isLoggedIn, isUserActive } from '../../../types/users';
+import { RestUser, UserActive, isLoggedIn, isUserActive } from '../../../types/users';
 import { searchAssignees } from '../utils';
 
 // exported for test
@@ -40,7 +40,7 @@ export interface AssigneeSelectProps {
   inputId: string;
 }
 
-function userToOption(user: RestUser) {
+function userToOption(user: RestUser | UserActive) {
   const userInfo = user.name || user.login;
   return {
     value: user.login,
@@ -58,7 +58,7 @@ export default function AssigneeSelect(props: AssigneeSelectProps) {
     isLoggedIn(currentUser) && issues.some((issue) => currentUser.login !== issue.assignee);
 
   const defaultOptions = allowCurrentUserSelection
-    ? [UNASSIGNED, userToOption(currentUser as unknown as RestUser)]
+    ? [UNASSIGNED, userToOption(currentUser)]
     : [UNASSIGNED];
 
   const controlLabel = assignee ? (
index b63ad7288eef6f036ff81d8d05cae010516cc393..798ec561001a23ef66bd70a5b90f5d0ab167bd4b 100644 (file)
@@ -25,8 +25,8 @@ import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
 import { throwGlobalError } from '../../../helpers/error';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { parseError } from '../../../helpers/request';
-import { useCreateUserMutation, useUpdateUserMutation } from '../../../queries/users';
+import { parseMessage } from '../../../helpers/request';
+import { usePostUserMutation, useUpdateUserMutation } from '../../../queries/users';
 import { RestUserDetailed } from '../../../types/users';
 import UserScmAccountInput from './UserScmAccountInput';
 
@@ -41,7 +41,7 @@ const INTERNAL_SERVER_ERROR = 500;
 export default function UserForm(props: Props) {
   const { user } = props;
 
-  const { mutate: createUser } = useCreateUserMutation();
+  const { mutate: createUser } = usePostUserMutation();
   const { mutate: updateUser } = useUpdateUserMutation();
 
   const [email, setEmail] = React.useState<string>(user?.email ?? '');
@@ -55,7 +55,7 @@ export default function UserForm(props: Props) {
     if (![BAD_REQUEST, INTERNAL_SERVER_ERROR].includes(response.status)) {
       throwGlobalError(response);
     } else {
-      parseError(response).then((errorMsg) => setError(errorMsg), throwGlobalError);
+      parseMessage(response).then((errorMsg) => setError(errorMsg), throwGlobalError);
     }
   };
 
@@ -66,7 +66,7 @@ export default function UserForm(props: Props) {
         login,
         name,
         password,
-        scmAccount: scmAccounts,
+        scmAccounts,
       },
       { onSuccess: props.onClose, onError: handleError }
     );
index 9b40c1f14ed5eee7b1b2678f52acf28fba288d5d..5a1537dfb816152989de83da759f3c51778ff4d9 100644 (file)
@@ -178,7 +178,7 @@ export function parseText(response: Response): Promise<string> {
 }
 
 /**
- * Parse response of failed request
+ * Parse error response of failed request
  */
 export function parseError(response: Response): Promise<string> {
   const DEFAULT_MESSAGE = translate('default_error_message');
@@ -187,6 +187,16 @@ export function parseError(response: Response): Promise<string> {
     .catch(() => DEFAULT_MESSAGE);
 }
 
+/**
+ * Parse message response of failed request
+ */
+export function parseMessage(response: Response): Promise<string> {
+  const DEFAULT_MESSAGE = translate('default_error_message');
+  return parseJSON(response)
+    .then(({ message }) => message)
+    .catch(() => DEFAULT_MESSAGE);
+}
+
 /**
  * Shortcut to do a GET request and return a Response
  */
@@ -275,7 +285,7 @@ export function post(url: string, data?: RequestData, bypassRedirect?: boolean):
 /**
  * Shortcut to do a DELETE request
  */
-export function deleteJSON(url: string, data?: RequestData): Promise<any> {
+export function deleteJSON(url: string, data?: RequestData): Promise<Response> {
   return request(url)
     .setMethod('DELETE')
     .setData(data)
index 51e3ce0acb9e4c14fa756545ecfce7d0818f8b1f..f74f5730f33931918f9695f3d5c9681cf9728737 100644 (file)
@@ -25,7 +25,7 @@ import {
   useQueryClient,
 } from '@tanstack/react-query';
 import { range } from 'lodash';
-import { createUser, deleteUser, getUsers, updateUser } from '../api/users';
+import { deleteUser, getUsers, postUser, updateUser } from '../api/users';
 import { RestUserBase } from '../types/users';
 
 const STALE_TIME = 4 * 60 * 1000;
@@ -65,12 +65,12 @@ export function useInvalidateUsersList() {
   return () => queryClient.invalidateQueries({ queryKey: ['user', 'list'] });
 }
 
-export function useCreateUserMutation() {
+export function usePostUserMutation() {
   const queryClient = useQueryClient();
 
   return useMutation({
-    mutationFn: async (data: Parameters<typeof createUser>[0]) => {
-      await createUser(data);
+    mutationFn: async (data: Parameters<typeof postUser>[0]) => {
+      await postUser(data);
     },
     onSuccess() {
       queryClient.invalidateQueries({ queryKey: ['user', 'list'] });