diff options
Diffstat (limited to 'app/assets/javascripts/invite_members/components/import_project_members_modal.vue')
-rw-r--r-- | app/assets/javascripts/invite_members/components/import_project_members_modal.vue | 144 |
1 files changed, 128 insertions, 16 deletions
diff --git a/app/assets/javascripts/invite_members/components/import_project_members_modal.vue b/app/assets/javascripts/invite_members/components/import_project_members_modal.vue index 66d4a9ccc07..5599ad276f0 100644 --- a/app/assets/javascripts/invite_members/components/import_project_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/import_project_members_modal.vue @@ -1,5 +1,5 @@ <script> -import { GlFormGroup, GlModal, GlSprintf } from '@gitlab/ui'; +import { GlFormGroup, GlModal, GlSprintf, GlAlert, GlCollapse, GlIcon, GlButton } from '@gitlab/ui'; import { uniqueId, isEmpty } from 'lodash'; import { importProjectMembers } from '~/api/projects_api'; import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants'; @@ -16,8 +16,10 @@ import { PROJECT_SELECT_LABEL_ID, IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_CATEGORY, IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_LABEL, + MEMBER_MODAL_LABELS, } from '../constants'; +import { responseFromSuccess } from '../utils/response_message_parser'; import UserLimitNotification from './user_limit_notification.vue'; import ProjectSelect from './project_select.vue'; @@ -27,6 +29,10 @@ export default { GlFormGroup, GlModal, GlSprintf, + GlAlert, + GlCollapse, + GlIcon, + GlButton, UserLimitNotification, ProjectSelect, }, @@ -60,6 +66,9 @@ export default { return { projectToBeImported: {}, invalidFeedbackMessage: '', + totalMembersCount: 0, + invalidMembers: {}, + isErrorsSectionExpanded: false, isLoading: false, }; }, @@ -94,6 +103,40 @@ export default { actionCancel() { return { text: this.$options.i18n.modalCancelButton }; }, + hasInvalidMembers() { + return !isEmpty(this.invalidMembers); + }, + memberErrorTitle() { + return sprintf( + s__( + 'InviteMembersModal|The following %{errorMembersLength} out of %{totalMembersCount} members could not be added', + ), + { errorMembersLength: this.errorList.length, totalMembersCount: this.totalMembersCount }, + ); + }, + errorList() { + return Object.entries(this.invalidMembers).map(([member, error]) => { + return { member, displayedMemberName: `@${member}`, message: error }; + }); + }, + errorsLimited() { + return this.errorList.slice(0, this.$options.errorsLimit); + }, + errorsExpanded() { + return this.errorList.slice(this.$options.errorsLimit); + }, + shouldErrorsSectionExpand() { + return Boolean(this.errorsExpanded.length); + }, + errorCollapseText() { + if (this.isErrorsSectionExpanded) { + return this.$options.labels.expandedErrors; + } + + return sprintf(this.$options.labels.collapsedErrors, { + count: this.errorsExpanded.length, + }); + }, }, mounted() { if (this.reloadPageOnSubmit) { @@ -113,21 +156,37 @@ export default { this.$root.$emit(BV_HIDE_MODAL, this.$options.modalId); }, resetFields() { + this.clearValidation(); this.invalidFeedbackMessage = ''; this.projectToBeImported = {}; }, - submitImport(e) { + async submitImport(event) { // We never want to hide when submitting - e.preventDefault(); + event.preventDefault(); this.isLoading = true; - return importProjectMembers(this.projectId, this.projectToBeImported.id) - .then(this.onInviteSuccess) - .catch(this.showErrorAlert) - .finally(() => { - this.isLoading = false; - this.projectToBeImported = {}; - }); + + try { + const response = await importProjectMembers(this.projectId, this.projectToBeImported.id); + + const { error, message } = responseFromSuccess(response); + + if (error) { + this.totalMembersCount = response.data.total_members_count; + this.showMemberErrors(message); + } else { + this.onInviteSuccess(); + } + } catch { + this.showErrorAlert(); + } finally { + this.isLoading = false; + this.projectToBeImported = {}; + } + }, + showMemberErrors(message) { + this.invalidMembers = message; + this.$refs.alerts.focus(); }, onInviteSuccess() { this.track('invite_successful'); @@ -151,6 +210,13 @@ export default { onClose() { this.track('click_x'); }, + clearValidation() { + this.invalidFeedbackMessage = ''; + this.invalidMembers = {}; + }, + toggleErrorExpansion() { + this.isErrorsSectionExpanded = !this.isErrorsSectionExpanded; + }, }, toastOptions() { return { @@ -173,8 +239,10 @@ export default { defaultError: s__('ImportAProjectModal|Unable to import project members'), successMessage: s__('ImportAProjectModal|Successfully imported'), }, + errorsLimit: 2, projectSelectLabelId: PROJECT_SELECT_LABEL_ID, modalId: uniqueId('import-a-project-modal-'), + labels: MEMBER_MODAL_LABELS, }; </script> @@ -186,18 +254,62 @@ export default { :title="$options.i18n.modalTitle" :action-primary="actionPrimary" :action-cancel="actionCancel" + data-testid="import-project-members-modal" no-focus-on-show @primary="submitImport" @hidden="resetFields" @cancel="onCancel" @close="onClose" > - <user-limit-notification - v-if="showUserLimitNotification" - class="gl-mb-5" - :limit-variant="limitVariant" - :users-limit-dataset="usersLimitDataset" - /> + <div ref="alerts" tabindex="-1"> + <gl-alert + v-if="hasInvalidMembers" + class="gl-mb-4" + variant="danger" + :dismissible="false" + :title="memberErrorTitle" + data-testid="alert-member-error" + > + {{ $options.labels.memberErrorListText }} + <ul class="gl-pl-5 gl-mb-0"> + <li v-for="error in errorsLimited" :key="error.member" data-testid="errors-limited-item"> + <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} + </li> + </ul> + <template v-if="shouldErrorsSectionExpand"> + <gl-collapse v-model="isErrorsSectionExpanded"> + <ul class="gl-pl-5 gl-mb-0"> + <li + v-for="error in errorsExpanded" + :key="error.member" + data-testid="errors-expanded-item" + > + <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} + </li> + </ul> + </gl-collapse> + <gl-button + class="gl-text-decoration-none! gl-shadow-none! gl-mt-3" + data-testid="accordion-button" + variant="link" + @click="toggleErrorExpansion" + > + {{ errorCollapseText }} + <gl-icon + name="chevron-down" + class="gl-transition-medium" + :class="{ 'gl-rotate-180': isErrorsSectionExpanded }" + /> + </gl-button> + </template> + </gl-alert> + <user-limit-notification + v-else-if="showUserLimitNotification" + class="gl-mb-5" + :limit-variant="limitVariant" + :users-limit-dataset="usersLimitDataset" + /> + </div> <p ref="modalIntro"> <gl-sprintf :message="modalIntro"> <template #strong="{ content }"> |