summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/invite_members
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-18 20:02:30 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-18 20:02:30 +0000
commit41fe97390ceddf945f3d967b8fdb3de4c66b7dea (patch)
tree9c8d89a8624828992f06d892cd2f43818ff5dcc8 /app/assets/javascripts/invite_members
parent0804d2dc31052fb45a1efecedc8e06ce9bc32862 (diff)
downloadgitlab-ce-41fe97390ceddf945f3d967b8fdb3de4c66b7dea.tar.gz
Add latest changes from gitlab-org/gitlab@14-9-stable-eev14.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/invite_members')
-rw-r--r--app/assets/javascripts/invite_members/components/invite_group_trigger.vue7
-rw-r--r--app/assets/javascripts/invite_members/components/invite_groups_modal.vue28
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue34
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue235
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_form.js7
-rw-r--r--app/assets/javascripts/invite_members/utils/get_invalid_feedback_message.js12
6 files changed, 196 insertions, 127 deletions
diff --git a/app/assets/javascripts/invite_members/components/invite_group_trigger.vue b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
index c08a4d75c59..424a9d3fabd 100644
--- a/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue
@@ -28,7 +28,12 @@ export default {
</script>
<template>
- <gl-button :class="classes" data-qa-selector="invite_a_group_button" @click="openModal">
+ <gl-button
+ :class="classes"
+ data-qa-selector="invite_a_group_button"
+ data-test-id="invite-group-button"
+ @click="openModal"
+ >
{{ displayText }}
</gl-button>
</template>
diff --git a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
index 6598000c464..f266d978ffa 100644
--- a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue
@@ -4,6 +4,7 @@ import Api from '~/api';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { GROUP_FILTERS, GROUP_MODAL_LABELS } from '../constants';
import eventHub from '../event_hub';
+import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message';
import GroupSelect from './group_select.vue';
import InviteModalBase from './invite_modal_base.vue';
@@ -55,6 +56,8 @@ export default {
},
data() {
return {
+ invalidFeedbackMessage: '',
+ isLoading: false,
modalId: uniqueId('invite-groups-modal-'),
groupToBeSharedWith: {},
};
@@ -83,13 +86,19 @@ export default {
});
},
methods: {
+ showInvalidFeedbackMessage(response) {
+ this.invalidFeedbackMessage = getInvalidFeedbackMessage(response);
+ },
openModal() {
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
},
closeModal() {
this.$root.$emit(BV_HIDE_MODAL, this.modalId);
},
- sendInvite({ onError, onSuccess, data: { accessLevel, expiresAt } }) {
+ sendInvite({ accessLevel, expiresAt }) {
+ this.invalidFeedbackMessage = '';
+ this.isLoading = true;
+
const apiShareWithGroup = this.isProject
? Api.projectShareWithGroup.bind(Api)
: Api.groupShareWithGroup.bind(Api);
@@ -101,18 +110,27 @@ export default {
expires_at: expiresAt,
})
.then(() => {
- onSuccess();
this.showSuccessMessage();
})
- .catch(onError);
+ .catch((e) => {
+ this.showInvalidFeedbackMessage(e);
+ })
+ .finally(() => {
+ this.isLoading = false;
+ });
},
resetFields() {
+ this.invalidFeedbackMessage = '';
+ this.isLoading = false;
this.groupToBeSharedWith = {};
},
showSuccessMessage() {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
this.closeModal();
},
+ clearValidation() {
+ this.invalidFeedbackMessage = '';
+ },
},
labels: GROUP_MODAL_LABELS,
};
@@ -129,10 +147,12 @@ export default {
:label-intro-text="labelIntroText"
:label-search-field="$options.labels.searchField"
:submit-disabled="inviteDisabled"
+ :invalid-feedback-message="invalidFeedbackMessage"
+ :is-loading="isLoading"
@reset="resetFields"
@submit="sendInvite"
>
- <template #select="{ clearValidation }">
+ <template #select>
<group-select
v-model="groupToBeSharedWith"
:access-levels="accessLevels"
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
index 6c0fc5caf26..be48a58d838 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -21,6 +21,7 @@ import {
} from '../constants';
import eventHub from '../event_hub';
import { responseMessageFromSuccess } from '../utils/response_message_parser';
+import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message';
import ModalConfetti from './confetti.vue';
import MembersTokenSelect from './members_token_select.vue';
@@ -84,6 +85,8 @@ export default {
},
data() {
return {
+ invalidFeedbackMessage: '',
+ isLoading: false,
modalId: uniqueId('invite-members-modal-'),
newUsersToInvite: [],
selectedTasksToBeDone: [],
@@ -152,6 +155,9 @@ export default {
}
},
methods: {
+ showInvalidFeedbackMessage(response) {
+ this.invalidFeedbackMessage = getInvalidFeedbackMessage(response);
+ },
partitionNewUsersToInvite() {
const [usersToInviteByEmail, usersToAddById] = partition(
this.newUsersToInvite,
@@ -176,7 +182,10 @@ export default {
const tracking = new ExperimentTracking(experimentName);
tracking.event(eventName);
},
- sendInvite({ onError, onSuccess, data: { accessLevel, expiresAt } }) {
+ sendInvite({ accessLevel, expiresAt }) {
+ this.isLoading = true;
+ this.invalidFeedbackMessage = '';
+
const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
const promises = [];
const baseData = {
@@ -220,19 +229,17 @@ export default {
const message = responseMessageFromSuccess(responses);
if (message) {
- onError({
- response: {
- data: {
- message,
- },
- },
+ this.showInvalidFeedbackMessage({
+ response: { data: { message } },
});
} else {
- onSuccess();
this.showSuccessMessage();
}
})
- .catch(onError);
+ .catch((e) => this.showInvalidFeedbackMessage(e))
+ .finally(() => {
+ this.isLoading = false;
+ });
},
trackinviteMembersForTask() {
const label = 'selected_tasks_to_be_done';
@@ -241,6 +248,8 @@ export default {
tracking.event(INVITE_MEMBERS_FOR_TASK.submit);
},
resetFields() {
+ this.isLoading = false;
+ this.invalidFeedbackMessage = '';
this.newUsersToInvite = [];
this.selectedTasksToBeDone = [];
[this.selectedTaskProject] = this.projects;
@@ -260,6 +269,9 @@ export default {
onAccessLevelUpdate(val) {
this.selectedAccessLevel = val;
},
+ clearValidation() {
+ this.invalidFeedbackMessage = '';
+ },
},
labels: MEMBER_MODAL_LABELS,
};
@@ -276,6 +288,8 @@ export default {
:label-search-field="$options.labels.searchField"
:form-group-description="$options.labels.placeHolder"
:submit-disabled="inviteDisabled"
+ :invalid-feedback-message="invalidFeedbackMessage"
+ :is-loading="isLoading"
@reset="resetFields"
@submit="sendInvite"
@access-level="onAccessLevelUpdate"
@@ -288,7 +302,7 @@ export default {
<span v-if="isCelebration">{{ $options.labels.modal.celebrate.intro }} </span>
<modal-confetti v-if="isCelebration" />
</template>
- <template #select="{ clearValidation, validationState, labelId }">
+ <template #select="{ validationState, labelId }">
<members-token-select
v-model="newUsersToInvite"
class="gl-mb-2"
diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index fc00f5b9343..bafbe94b8bd 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -10,19 +10,27 @@ import {
GlButton,
GlFormInput,
} from '@gitlab/ui';
-import { unescape } from 'lodash';
-import { sanitize } from '~/lib/dompurify';
import { sprintf } from '~/locale';
+import ContentTransition from '~/vue_shared/components/content_transition.vue';
import {
ACCESS_LEVEL,
ACCESS_EXPIRE_DATE,
- INVALID_FEEDBACK_MESSAGE_DEFAULT,
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT,
HEADER_CLOSE_LABEL,
} from '../constants';
-import { responseMessageFromError } from '../utils/response_message_parser';
+
+const DEFAULT_SLOT = 'default';
+const DEFAULT_SLOTS = [
+ {
+ key: DEFAULT_SLOT,
+ attributes: {
+ class: 'invite-modal-content',
+ 'data-testid': 'invite-modal-initial-content',
+ },
+ },
+];
export default {
components: {
@@ -35,6 +43,7 @@ export default {
GlSprintf,
GlButton,
GlFormInput,
+ ContentTransition,
},
inheritAttrs: false,
props: {
@@ -80,14 +89,37 @@ export default {
required: false,
default: false,
},
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ invalidFeedbackMessage: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ submitButtonText: {
+ type: String,
+ required: false,
+ default: INVITE_BUTTON_TEXT,
+ },
+ currentSlot: {
+ type: String,
+ required: false,
+ default: DEFAULT_SLOT,
+ },
+ extraSlots: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
// Be sure to check out reset!
return {
- invalidFeedbackMessage: '',
selectedAccessLevel: this.defaultAccessLevel,
selectedDate: undefined,
- isLoading: false,
minDate: new Date(),
};
},
@@ -106,6 +138,9 @@ export default {
(key) => this.accessLevels[key] === Number(this.selectedAccessLevel),
);
},
+ contentSlots() {
+ return [...DEFAULT_SLOTS, ...(this.extraSlots || [])];
+ },
},
watch: {
selectedAccessLevel: {
@@ -116,16 +151,9 @@ export default {
},
},
methods: {
- showInvalidFeedbackMessage(response) {
- const message = this.unescapeMsg(responseMessageFromError(response));
-
- this.invalidFeedbackMessage = message || INVALID_FEEDBACK_MESSAGE_DEFAULT;
- },
reset() {
// This component isn't necessarily disposed,
// so we might need to reset it's state.
- this.isLoading = false;
- this.invalidFeedbackMessage = '';
this.selectedAccessLevel = this.defaultAccessLevel;
this.selectedDate = undefined;
@@ -135,33 +163,15 @@ export default {
this.reset();
this.$refs.modal.hide();
},
- clearValidation() {
- this.invalidFeedbackMessage = '';
- },
changeSelectedItem(item) {
this.selectedAccessLevel = item;
},
submit() {
- this.isLoading = true;
- this.invalidFeedbackMessage = '';
-
this.$emit('submit', {
- onSuccess: () => {
- this.isLoading = false;
- },
- onError: (...args) => {
- this.isLoading = false;
- this.showInvalidFeedbackMessage(...args);
- },
- data: {
- accessLevel: this.selectedAccessLevel,
- expiresAt: this.selectedDate,
- },
+ accessLevel: this.selectedAccessLevel,
+ expiresAt: this.selectedDate,
});
},
- unescapeMsg(message) {
- return unescape(sanitize(message, { ALLOWED_TAGS: [] }));
- },
},
HEADER_CLOSE_LABEL,
ACCESS_EXPIRE_DATE,
@@ -169,6 +179,7 @@ export default {
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT,
+ DEFAULT_SLOT,
};
</script>
@@ -185,91 +196,105 @@ export default {
@close="reset"
@hide="reset"
>
- <div class="gl-display-flex" data-testid="modal-base-intro-text">
- <slot name="intro-text-before"></slot>
- <p>
- <gl-sprintf :message="introText">
- <template #strong="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </p>
- <slot name="intro-text-after"></slot>
- </div>
-
- <gl-form-group
- :invalid-feedback="invalidFeedbackMessage"
- :state="validationState"
- :description="formGroupDescription"
- data-testid="members-form-group"
+ <content-transition
+ class="gl-display-grid"
+ transition-name="invite-modal-transition"
+ :slots="contentSlots"
+ :current-slot="currentSlot"
>
- <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
- <slot
- name="select"
- v-bind="{ clearValidation, validationState, labelId: selectLabelId }"
- ></slot>
- </gl-form-group>
+ <template #[$options.DEFAULT_SLOT]>
+ <div class="gl-display-flex" data-testid="modal-base-intro-text">
+ <slot name="intro-text-before"></slot>
+ <p>
+ <gl-sprintf :message="introText">
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ <slot name="intro-text-after"></slot>
+ </div>
- <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-dropdown
- class="gl-shadow-none gl-w-full"
- data-qa-selector="access_level_dropdown"
- v-bind="$attrs"
- :text="selectedRoleName"
- >
- <template v-for="(key, item) in accessLevels">
- <gl-dropdown-item
- :key="key"
- active-class="is-active"
- is-check-item
- :is-checked="key === selectedAccessLevel"
- @click="changeSelectedItem(key)"
- >
- <div>{{ item }}</div>
- </gl-dropdown-item>
- </template>
- </gl-dropdown>
- </div>
+ <gl-form-group
+ :invalid-feedback="invalidFeedbackMessage"
+ :state="validationState"
+ :description="formGroupDescription"
+ data-testid="members-form-group"
+ >
+ <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
+ <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
+ </gl-form-group>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-sprintf :message="$options.READ_MORE_TEXT">
- <template #link="{ content }">
- <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
+ <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-dropdown
+ class="gl-shadow-none gl-w-full"
+ data-qa-selector="access_level_dropdown"
+ v-bind="$attrs"
+ :text="selectedRoleName"
+ >
+ <template v-for="(key, item) in accessLevels">
+ <gl-dropdown-item
+ :key="key"
+ active-class="is-active"
+ is-check-item
+ :is-checked="key === selectedAccessLevel"
+ @click="changeSelectedItem(key)"
+ >
+ <div>{{ item }}</div>
+ </gl-dropdown-item>
+ </template>
+ </gl-dropdown>
+ </div>
- <label class="gl-mt-5 gl-display-block" for="expires_at">{{
- $options.ACCESS_EXPIRE_DATE
- }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
- <gl-datepicker
- v-model="selectedDate"
- class="gl-display-inline!"
- :min-date="minDate"
- :target="null"
- >
- <template #default="{ formattedDate }">
- <gl-form-input class="gl-w-full" :value="formattedDate" :placeholder="__(`YYYY-MM-DD`)" />
- </template>
- </gl-datepicker>
- </div>
- <slot name="form-after"></slot>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-sprintf :message="$options.READ_MORE_TEXT">
+ <template #link="{ content }">
+ <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ <label class="gl-mt-5 gl-display-block" for="expires_at">{{
+ $options.ACCESS_EXPIRE_DATE
+ }}</label>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
+ <gl-datepicker
+ v-model="selectedDate"
+ class="gl-display-inline!"
+ :min-date="minDate"
+ :target="null"
+ >
+ <template #default="{ formattedDate }">
+ <gl-form-input
+ class="gl-w-full"
+ :value="formattedDate"
+ :placeholder="__(`YYYY-MM-DD`)"
+ />
+ </template>
+ </gl-datepicker>
+ </div>
+ <slot name="form-after"></slot>
+ </template>
+ <template v-for="{ key } in extraSlots" #[key]>
+ <slot :name="key"></slot>
+ </template>
+ </content-transition>
<template #modal-footer>
- <gl-button data-testid="cancel-button" @click="closeModal">
- {{ $options.CANCEL_BUTTON_TEXT }}
- </gl-button>
+ <slot name="cancel-button">
+ <gl-button data-testid="cancel-button" @click="closeModal">
+ {{ $options.CANCEL_BUTTON_TEXT }}
+ </gl-button>
+ </slot>
<gl-button
:disabled="submitDisabled"
:loading="isLoading"
- variant="success"
+ variant="confirm"
data-qa-selector="invite_button"
data-testid="invite-button"
@click="submit"
>
- {{ $options.INVITE_BUTTON_TEXT }}
+ {{ submitButtonText }}
</gl-button>
</template>
</gl-modal>
diff --git a/app/assets/javascripts/invite_members/init_invite_members_form.js b/app/assets/javascripts/invite_members/init_invite_members_form.js
deleted file mode 100644
index 5f8688755ba..00000000000
--- a/app/assets/javascripts/invite_members/init_invite_members_form.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
-
-// This is only used when `invite_members_group_modal` feature flag is disabled.
-// This file can be removed when `invite_members_group_modal` feature flag is removed
-export default () => {
- disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
-};
diff --git a/app/assets/javascripts/invite_members/utils/get_invalid_feedback_message.js b/app/assets/javascripts/invite_members/utils/get_invalid_feedback_message.js
new file mode 100644
index 00000000000..62f66d009dc
--- /dev/null
+++ b/app/assets/javascripts/invite_members/utils/get_invalid_feedback_message.js
@@ -0,0 +1,12 @@
+import { unescape } from 'lodash';
+import { sanitize } from '~/lib/dompurify';
+import { INVALID_FEEDBACK_MESSAGE_DEFAULT } from '../constants';
+import { responseMessageFromError } from './response_message_parser';
+
+const unescapeMsg = (message) => unescape(sanitize(message, { ALLOWED_TAGS: [] }));
+
+export const getInvalidFeedbackMessage = (response) => {
+ const message = unescapeMsg(responseMessageFromError(response));
+
+ return message || INVALID_FEEDBACK_MESSAGE_DEFAULT;
+};