diff --git a/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_user_list.vue b/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_user_list.vue index 8804336f2657332868849513b7e891ae8ed6e7ee..1b845696eaa82fbebaf2484aab441b1fb74deb7c 100644 --- a/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_user_list.vue +++ b/ee/app/assets/javascripts/usage_quotas/seats/components/subscription_user_list.vue @@ -163,6 +163,9 @@ export default { if (this.removedBillableMemberId === user?.id) return true; return this.recentlyDeletedMembersIds.includes(user?.id); }, + isLastOwner(user) { + return user.is_last_owner; + }, getRecentlyDeletedMembersIds() { try { if (this.hasLocalStorageExpired()) { @@ -174,6 +177,9 @@ export default { return []; } }, + removeButtonDisabled(user) { + return this.isUserRemoved(user) || this.isLastOwner(user); + }, }, i18n: { emailNotVisibleTooltipText, @@ -275,35 +281,40 @@ export default { </div> </template> - <template #cell(lastActivityTime)="data"> + <template #cell(lastActivityTime)="{ item }"> <span data-testid="last_activity_on"> - {{ data.item.user.last_activity_on ? data.item.user.last_activity_on : __('Never') }} + {{ item.user.last_activity_on ? item.user.last_activity_on : __('Never') }} </span> </template> - <template #cell(lastLoginAt)="data"> + <template #cell(lastLoginAt)="{ item }"> <span data-testid="last_login_at"> - {{ formatLastLoginAt(data.item.user.last_login_at) }} + {{ formatLastLoginAt(item.user.last_login_at) }} </span> </template> - <template #cell(actions)="data"> - <span :id="`remove-member-${data.item.user.id}`" class="gl-inline-block" tabindex="0"> + <template #cell(actions)="{ item }"> + <span :id="`remove-member-${item.user.id}`" class="gl-inline-block" tabindex="0"> <gl-button v-gl-modal="$options.removeBillableMemberModalId" category="secondary" variant="danger" data-testid="remove-user" - :disabled="isUserRemoved(data.item.user)" - @click="displayRemoveMemberModal(data.item.user)" + :disabled="removeButtonDisabled(item.user)" + @click="displayRemoveMemberModal(item.user)" > {{ __('Remove user') }} </gl-button> <gl-tooltip - v-if="isUserRemoved(data.item.user)" - :target="`remove-member-${data.item.user.id}`" + v-if="removeButtonDisabled(item.user)" + :target="`remove-member-${item.user.id}`" + data-testid="remove-user-tooltip" > - {{ s__('Billing|This user is scheduled for removal.') }}</gl-tooltip + {{ + isLastOwner(item.user) + ? s__('Billing|Cannot remove the last owner.') + : s__('Billing|This user is scheduled for removal.') + }}</gl-tooltip > </span> </template> diff --git a/ee/spec/frontend/usage_quotas/seats/components/__snapshots__/subscription_user_list_spec.js.snap b/ee/spec/frontend/usage_quotas/seats/components/__snapshots__/subscription_user_list_spec.js.snap index 4f0c37ba2363919d1ae37a7a53f649458fd0800e..7d9f9d6fcd0d4583dc0635f5dd4c003bd6f030cd 100644 --- a/ee/spec/frontend/usage_quotas/seats/components/__snapshots__/subscription_user_list_spec.js.snap +++ b/ee/spec/frontend/usage_quotas/seats/components/__snapshots__/subscription_user_list_spec.js.snap @@ -6,42 +6,54 @@ exports[`SubscriptionUserList renders table content renders the correct data 1`] "email": "administrator@email.com", "lastActivityOn": "2020-03-01", "lastLoginAt": "2022-11-10 10:58:05", + "removeUserButtonDisabled": true, "removeUserButtonExists": true, + "removeUserButtonTooltip": "Cannot remove the last owner.", "tooltip": undefined, }, { "email": "agustin_walker@email.com", "lastActivityOn": "2020-03-01", "lastLoginAt": "2021-01-20 10:58:05", + "removeUserButtonDisabled": false, "removeUserButtonExists": true, + "removeUserButtonTooltip": undefined, "tooltip": undefined, }, { "email": "Private", "lastActivityOn": "Never", "lastLoginAt": "Never", + "removeUserButtonDisabled": false, "removeUserButtonExists": true, + "removeUserButtonTooltip": undefined, "tooltip": "An email address is only visible for users with public emails.", }, { "email": "jdoe@email.com", "lastActivityOn": "Never", "lastLoginAt": "Never", + "removeUserButtonDisabled": false, "removeUserButtonExists": true, + "removeUserButtonTooltip": undefined, "tooltip": undefined, }, { "email": "jsnow@email.com", "lastActivityOn": "2020-03-01", "lastLoginAt": "Never", + "removeUserButtonDisabled": false, "removeUserButtonExists": true, + "removeUserButtonTooltip": undefined, "tooltip": undefined, }, { "email": "current_user@email.com", "lastActivityOn": "2020-03-01", "lastLoginAt": "Never", + "removeUserButtonDisabled": false, "removeUserButtonExists": true, + "removeUserButtonTooltip": undefined, "tooltip": undefined, }, ] diff --git a/ee/spec/frontend/usage_quotas/seats/components/subscription_user_list_spec.js b/ee/spec/frontend/usage_quotas/seats/components/subscription_user_list_spec.js index b84528c0499b1b370e9753790774358c704cea6a..5cd91efa9aa5775bedcd84b3c8fb40a5f073fcdb 100644 --- a/ee/spec/frontend/usage_quotas/seats/components/subscription_user_list_spec.js +++ b/ee/spec/frontend/usage_quotas/seats/components/subscription_user_list_spec.js @@ -1,6 +1,5 @@ import { GlPagination, - GlButton, GlTable, GlAvatarLink, GlAvatarLabeled, @@ -120,14 +119,20 @@ describe('SubscriptionUserList', () => { const findErrorModal = () => wrapper.findComponent(GlModal); const serializeTableRow = (rowWrapper) => { - const emailWrapper = rowWrapper.find('[data-testid="email"]'); + const extendedRowWrapper = extendedWrapper(rowWrapper); + const emailWrapper = extendedRowWrapper.findByTestId('email'); return { email: emailWrapper.text(), tooltip: emailWrapper.find('span').attributes('title'), - removeUserButtonExists: rowWrapper.findComponent(GlButton).exists(), - lastActivityOn: rowWrapper.find('[data-testid="last_activity_on"]').text(), - lastLoginAt: rowWrapper.find('[data-testid="last_login_at"]').text(), + removeUserButtonExists: extendedRowWrapper.findByTestId('remove-user').exists(), + removeUserButtonDisabled: + extendedRowWrapper.findByTestId('remove-user').attributes('disabled') === 'disabled', + removeUserButtonTooltip: extendedRowWrapper.findByTestId('remove-user-tooltip').exists() + ? extendedRowWrapper.findByTestId('remove-user-tooltip').text() + : undefined, + lastActivityOn: extendedRowWrapper.findByTestId('last_activity_on').text(), + lastLoginAt: extendedRowWrapper.findByTestId('last_login_at').text(), }; }; @@ -213,7 +218,7 @@ describe('SubscriptionUserList', () => { }); describe('when removing a billable user', () => { - const [{ user }] = mockTableItems; + const { user } = mockTableItems[0]; describe('with billableMemberAsyncDeletion enabled', () => { beforeEach(() => { @@ -273,7 +278,7 @@ describe('SubscriptionUserList', () => { }); describe('when the removed billable user is set', () => { - const selectedItem = 0; + const selectedItem = 1; const { user } = mockTableItems[selectedItem]; beforeEach(() => { @@ -285,7 +290,7 @@ describe('SubscriptionUserList', () => { }); it('does not disable unrelated remove button', () => { - expect(findAllRemoveUserItems().at(1).attributes().disabled).toBeUndefined(); + expect(findAllRemoveUserItems().at(2).attributes().disabled).toBeUndefined(); }); it('shows a tooltip for related users', () => { @@ -294,8 +299,8 @@ describe('SubscriptionUserList', () => { ); }); - it('does snot show a tooltip for unrelated user', () => { - const [, { user: nonRemovedUser }] = mockTableItems; + it('does not show a tooltip for unrelated user', () => { + const { user: nonRemovedUser } = mockTableItems[2]; expect(findRemoveMemberItem(nonRemovedUser.id).findComponent(GlTooltip).exists()).toBe( false, @@ -322,7 +327,7 @@ describe('SubscriptionUserList', () => { }); describe('when the removed billable user is in local storage', () => { - const selectedItem = 0; + const selectedItem = 1; const { user } = mockTableItems[selectedItem]; beforeEach(() => { @@ -336,7 +341,7 @@ describe('SubscriptionUserList', () => { }); it('does not disable unrelated remove button', () => { - expect(findAllRemoveUserItems().at(1).attributes().disabled).toBeUndefined(); + expect(findAllRemoveUserItems().at(3).attributes().disabled).toBeUndefined(); }); it('shows a tooltip for related users', () => { @@ -345,8 +350,8 @@ describe('SubscriptionUserList', () => { ); }); - it('does snot show a tooltip for unrelated user', () => { - const [, { user: nonRemovedUser }] = mockTableItems; + it('does not show a tooltip for unrelated user', () => { + const { user: nonRemovedUser } = mockTableItems[2]; expect(findRemoveMemberItem(nonRemovedUser.id).findComponent(GlTooltip).exists()).toBe( false, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e34d594479ae1f19519a388e7d63bc7c3ebc135b..c4722388e45148714590c7976bb4b486eb77265a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9547,6 +9547,9 @@ msgstr "" msgid "Billing|Awaiting member signup" msgstr "" +msgid "Billing|Cannot remove the last owner." +msgstr "" + msgid "Billing|Cannot remove user" msgstr ""