From 3545d9ace2eb210c0c3381e81bbaf95e9d73b3df Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Fri, 11 Aug 2023 11:51:52 +0200 Subject: [PATCH] SONAR-19979 Allow SCM account to be update on UI --- .../src/main/js/api/mocks/UsersServiceMock.ts | 2 +- server/sonar-web/src/main/js/api/users.ts | 16 +++--- .../src/main/js/apps/users/Header.tsx | 4 +- .../js/apps/users/__tests__/UsersApp-it.tsx | 28 ++++++++-- .../js/apps/users/components/UserActions.tsx | 52 ++++++++----------- .../js/apps/users/components/UserForm.tsx | 27 +++++----- .../js/apps/users/components/UserListItem.tsx | 9 ++-- .../resources/org/sonar/l10n/core.properties | 1 + 8 files changed, 79 insertions(+), 60 deletions(-) diff --git a/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts index ad4cf06f831..caca8dd61dc 100644 --- a/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts @@ -286,7 +286,7 @@ export default class UsersServiceMock { return Promise.reject('No such user'); } Object.assign(user, { - ...omitBy({ name, email, scmAccount }, isUndefined), + ...omitBy({ name, email, scmAccounts: scmAccount }, isUndefined), }); return this.reply({ user }); }; diff --git a/server/sonar-web/src/main/js/api/users.ts b/server/sonar-web/src/main/js/api/users.ts index 73812ab046f..e9e8ea5a800 100644 --- a/server/sonar-web/src/main/js/api/users.ts +++ b/server/sonar-web/src/main/js/api/users.ts @@ -106,12 +106,16 @@ export function postUser(data: { return postJSONBody('/api/v2/users', data); } -export function updateUser(data: { - email?: string; - login: string; - name?: string; - scmAccount: string[]; -}): Promise<{ user: User }> { +type UpdateUserArg = + | { + email?: string; + login: string; + name?: string; + scmAccount: string[]; + } + | { login: string; scmAccount: string[] }; + +export function updateUser(data: UpdateUserArg): Promise<{ user: User }> { return postJSON('/api/users/update', { ...data, scmAccount: data.scmAccount.length > 0 ? data.scmAccount : '', diff --git a/server/sonar-web/src/main/js/apps/users/Header.tsx b/server/sonar-web/src/main/js/apps/users/Header.tsx index 4dc19d14ff4..109802f6e39 100644 --- a/server/sonar-web/src/main/js/apps/users/Header.tsx +++ b/server/sonar-web/src/main/js/apps/users/Header.tsx @@ -65,7 +65,9 @@ export default function Header(props: Props) { /> )} - {openUserForm && setOpenUserForm(false)} />} + {openUserForm && ( + setOpenUserForm(false)} isInstanceManaged={false} /> + )} ); } diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx index 73e454c64a3..702e348f7c4 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx @@ -441,14 +441,36 @@ describe('in manage mode', () => { expect(ui.bobUpdateGroupButton.query()).not.toBeInTheDocument(); }); - it('should not be able to update / change password / deactivate a managed user', async () => { + it('should not be able to update scm account', async () => { + const user = userEvent.setup(); + renderUsersApp(); await act(async () => expect(await ui.bobRow.find()).toBeInTheDocument()); - expect(ui.bobUpdateButton.query()).not.toBeInTheDocument(); + expect(ui.bobUpdateButton.get()).toBeInTheDocument(); + + await user.click(ui.bobUpdateButton.get()); + + expect( + ui.bobRow.byRole('button', { name: 'users.deactivate' }).query() + ).not.toBeInTheDocument(); + expect( + ui.bobRow.byRole('button', { name: 'my_profile.password.title' }).query() + ).not.toBeInTheDocument(); + + await user.click(await ui.bobRow.byRole('button', { name: 'update_scm' }).get()); + + expect(ui.userNameInput.get()).toBeDisabled(); + expect(ui.emailInput.get()).toBeDisabled(); + + await user.click(ui.scmAddButton.get()); + await user.type(ui.dialogSCMInput().get(), 'SCM'); + await act(() => user.click(ui.updateButton.get())); + + expect(await ui.bobRow.byText(/SCM/).find()).toBeInTheDocument(); }); - it('should ONLY be able to deactivate a local user', async () => { + it('should be able to deactivate a local user', async () => { const user = userEvent.setup(); renderUsersApp(); diff --git a/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx b/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx index 8d9937de08b..e71502ae7b9 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx @@ -38,43 +38,27 @@ export default function UserActions(props: Props) { const [openForm, setOpenForm] = React.useState(undefined); - const isInstanceManaged = () => { - return manageProvider !== undefined; - }; + const isInstanceManaged = manageProvider !== undefined; - const isUserLocal = () => { - return isInstanceManaged() && !user.managed; - }; - - const isUserManaged = () => { - return isInstanceManaged() && user.managed; - }; - - if (isUserManaged()) { - return null; - } + const isUserLocal = isInstanceManaged && !user.managed; return ( <> - {!isInstanceManaged() && ( - <> - setOpenForm('update')}> - {translate('update_details')} - - {user.local && ( - setOpenForm('password')} - > - {translate('my_profile.password.title')} - - )} - + setOpenForm('update')}> + {isInstanceManaged ? translate('update_scm') : translate('update_details')} + + {!isInstanceManaged && user.local && ( + setOpenForm('password')} + > + {translate('my_profile.password.title')} + )} - {isUserActive(user) && !isInstanceManaged() && } - {isUserActive(user) && (!isInstanceManaged() || isUserLocal()) && ( + {isUserActive(user) && !isInstanceManaged && } + {isUserActive(user) && (!isInstanceManaged || isUserLocal) && ( setOpenForm(undefined)} user={user} /> )} - {openForm === 'update' && setOpenForm(undefined)} user={user} />} + {openForm === 'update' && ( + setOpenForm(undefined)} + user={user} + isInstanceManaged={isInstanceManaged} + /> + )} ); } diff --git a/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx b/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx index 798ec561001..a605cbdd5c2 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx @@ -33,13 +33,14 @@ import UserScmAccountInput from './UserScmAccountInput'; export interface Props { onClose: () => void; user?: RestUserDetailed; + isInstanceManaged: boolean; } const BAD_REQUEST = 400; const INTERNAL_SERVER_ERROR = 500; export default function UserForm(props: Props) { - const { user } = props; + const { user, isInstanceManaged } = props; const { mutate: createUser } = usePostUserMutation(); const { mutate: updateUser } = useUpdateUserMutation(); @@ -76,12 +77,14 @@ export default function UserForm(props: Props) { const { user } = props; updateUser( - { - email: user?.local ? email : undefined, - login, - name: user?.local ? name : undefined, - scmAccount: scmAccounts, - }, + isInstanceManaged + ? { scmAccount: scmAccounts, login } + : { + email: user?.local ? email : undefined, + login, + name: user?.local ? name : undefined, + scmAccount: scmAccounts, + }, { onSuccess: props.onClose, onError: handleError } ); }; @@ -140,7 +143,7 @@ export default function UserForm(props: Props) { minLength={3} name="login" onChange={(e) => setLogin(e.currentTarget.value)} - required + required={!isInstanceManaged} type="text" value={login} /> @@ -150,17 +153,17 @@ export default function UserForm(props: Props) {
setName(e.currentTarget.value)} - required + required={!isInstanceManaged} type="text" value={name} /> @@ -169,7 +172,7 @@ export default function UserForm(props: Props) { - {(manageProvider === undefined || !managed) && ( - - - - )} + + + {openTokenForm && setOpenTokenForm(false)} user={user} />} {openGroupForm && setOpenGroupForm(false)} user={user} />} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 4272c60e148..2fcfc18f680 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -323,6 +323,7 @@ since_previous_version_detailed.short=\u0394 version ({0}) this_name_is_already_taken=This name is already taken. tooltip_is_interactive=This is a tooltip with interactive elements. Use the TAB key to cycle through the interactive elements. update_details=Update details +update_scm=Update SCM details work_duration.x_days={0}d work_duration.x_hours={0}h work_duration.x_minutes={0}min -- 2.39.5