summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/invite_members
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 08:27:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 08:27:35 +0000
commit7e9c479f7de77702622631cff2628a9c8dcbc627 (patch)
treec8f718a08e110ad7e1894510980d2155a6549197 /app/assets/javascripts/invite_members
parente852b0ae16db4052c1c567d9efa4facc81146e88 (diff)
downloadgitlab-ce-7e9c479f7de77702622631cff2628a9c8dcbc627.tar.gz
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/invite_members')
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue85
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue120
-rw-r--r--app/assets/javascripts/invite_members/constants.js1
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js1
4 files changed, 172 insertions, 35 deletions
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 d2ea14a658b..b55ef77ae5d 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -6,13 +6,13 @@ import {
GlDatepicker,
GlLink,
GlSprintf,
- GlSearchBoxByType,
GlButton,
GlFormInput,
} from '@gitlab/ui';
import eventHub from '../event_hub';
-import { s__, sprintf } from '~/locale';
+import { s__, __, sprintf } from '~/locale';
import Api from '~/api';
+import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
export default {
name: 'InviteMembersModal',
@@ -23,16 +23,20 @@ export default {
GlDropdown,
GlDropdownItem,
GlSprintf,
- GlSearchBoxByType,
GlButton,
GlFormInput,
+ MembersTokenSelect,
},
props: {
- groupId: {
+ id: {
type: String,
required: true,
},
- groupName: {
+ isProject: {
+ type: Boolean,
+ required: true,
+ },
+ name: {
type: String,
required: true,
},
@@ -59,9 +63,16 @@ export default {
};
},
computed: {
+ inviteToName() {
+ return this.name.toUpperCase();
+ },
+ inviteToType() {
+ return this.isProject ? __('project') : __('group');
+ },
introText() {
- return sprintf(s__("InviteMembersModal|You're inviting members to the %{group_name} group"), {
- group_name: this.groupName,
+ return sprintf(s__("InviteMembersModal|You're inviting members to the %{name} %{type}"), {
+ name: this.inviteToName,
+ type: this.inviteToType,
});
},
toastOptions() {
@@ -110,13 +121,14 @@ export default {
this.selectedAccessLevel = item;
},
submitForm(formData) {
- return Api.inviteGroupMember(this.groupId, formData)
- .then(() => {
- this.showToastMessageSuccess();
- })
- .catch(error => {
- this.showToastMessageError(error);
- });
+ if (this.isProject) {
+ return Api.inviteProjectMembers(this.id, formData)
+ .then(this.showToastMessageSuccess)
+ .catch(this.showToastMessageError);
+ }
+ return Api.inviteGroupMember(this.id, formData)
+ .then(this.showToastMessageSuccess)
+ .catch(this.showToastMessageError);
},
showToastMessageSuccess() {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
@@ -129,44 +141,45 @@ export default {
},
labels: {
modalTitle: s__('InviteMembersModal|Invite team members'),
- userToInvite: s__('InviteMembersModal|GitLab member or Email address'),
+ newUsersToInvite: s__('InviteMembersModal|GitLab member or Email address'),
userPlaceholder: s__('InviteMembersModal|Search for members to invite'),
accessLevel: s__('InviteMembersModal|Choose a role permission'),
accessExpireDate: s__('InviteMembersModal|Access expiration date (optional)'),
- toastMessageSuccessful: s__('InviteMembersModal|Users were succesfully added'),
- toastMessageUnsuccessful: s__('InviteMembersModal|User not invited. Feature coming soon!'),
+ toastMessageSuccessful: s__('InviteMembersModal|Members were successfully added'),
+ toastMessageUnsuccessful: s__('InviteMembersModal|Some of the members could not be added'),
readMoreText: s__(`InviteMembersModal|%{linkStart}Read more%{linkEnd} about role permissions`),
inviteButtonText: s__('InviteMembersModal|Invite'),
cancelButtonText: s__('InviteMembersModal|Cancel'),
+ headerCloseLabel: s__('InviteMembersModal|Close invite team members'),
},
+ membersTokenSelectLabelId: 'invite-members-input',
};
</script>
<template>
- <gl-modal :modal-id="modalId" size="sm" :title="$options.labels.modalTitle">
+ <gl-modal
+ :modal-id="modalId"
+ size="sm"
+ :title="$options.labels.modalTitle"
+ :header-close-label="$options.labels.headerCloseLabel"
+ >
<div class="gl-ml-5 gl-mr-5">
<div>{{ introText }}</div>
- <label class="gl-font-weight-bold gl-mt-5">{{ $options.labels.userToInvite }}</label>
+ <label :id="$options.membersTokenSelectLabelId" class="gl-font-weight-bold gl-mt-5">{{
+ $options.labels.newUsersToInvite
+ }}</label>
<div class="gl-mt-2">
- <gl-search-box-by-type
+ <members-token-select
v-model="newUsersToInvite"
+ :label="$options.labels.newUsersToInvite"
+ :aria-labelledby="$options.membersTokenSelectLabelId"
:placeholder="$options.labels.userPlaceholder"
- type="text"
- autocomplete="off"
- autocorrect="off"
- autocapitalize="off"
- spellcheck="false"
/>
</div>
<label class="gl-font-weight-bold gl-mt-5">{{ $options.labels.accessLevel }}</label>
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-dropdown
- menu-class="dropdown-menu-selectable"
- class="gl-shadow-none gl-w-full"
- v-bind="$attrs"
- :text="selectedRoleName"
- >
+ <gl-dropdown class="gl-shadow-none gl-w-full" v-bind="$attrs" :text="selectedRoleName">
<template v-for="(key, item) in accessLevels">
<gl-dropdown-item
:key="key"
@@ -215,9 +228,13 @@ export default {
{{ $options.labels.cancelButtonText }}
</gl-button>
<div class="gl-mr-3"></div>
- <gl-button ref="inviteButton" variant="success" @click="sendInvite">{{
- $options.labels.inviteButtonText
- }}</gl-button>
+ <gl-button
+ ref="inviteButton"
+ :disabled="!newUsersToInvite"
+ variant="success"
+ @click="sendInvite"
+ >{{ $options.labels.inviteButtonText }}</gl-button
+ >
</div>
</template>
</gl-modal>
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
new file mode 100644
index 00000000000..aed2e5e3236
--- /dev/null
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -0,0 +1,120 @@
+<script>
+import { debounce } from 'lodash';
+import { GlTokenSelector, GlAvatar, GlAvatarLabeled } from '@gitlab/ui';
+import { USER_SEARCH_DELAY } from '../constants';
+import Api from '~/api';
+
+export default {
+ components: {
+ GlTokenSelector,
+ GlAvatar,
+ GlAvatarLabeled,
+ },
+ props: {
+ placeholder: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ ariaLabelledby: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ query: '',
+ users: [],
+ selectedTokens: [],
+ hasBeenFocused: false,
+ hideDropdownWithNoItems: true,
+ };
+ },
+ computed: {
+ newUsersToInvite() {
+ return this.selectedTokens
+ .map(obj => {
+ return obj.id;
+ })
+ .join(',');
+ },
+ placeholderText() {
+ if (this.selectedTokens.length === 0) {
+ return this.placeholder;
+ }
+ return '';
+ },
+ },
+ methods: {
+ handleTextInput(query) {
+ this.hideDropdownWithNoItems = false;
+ this.query = query;
+ this.loading = true;
+ this.retrieveUsers(query);
+ },
+ retrieveUsers: debounce(function debouncedRetrieveUsers() {
+ return Api.users(this.query, this.$options.queryOptions)
+ .then(response => {
+ this.users = response.data.map(token => ({
+ id: token.id,
+ name: token.name,
+ username: token.username,
+ avatar_url: token.avatar_url,
+ }));
+ this.loading = false;
+ })
+ .catch(() => {
+ this.loading = false;
+ });
+ }, USER_SEARCH_DELAY),
+ handleInput() {
+ this.$emit('input', this.newUsersToInvite);
+ },
+ handleBlur() {
+ this.hideDropdownWithNoItems = false;
+ },
+ handleFocus() {
+ // The modal auto-focuses on the input when opened.
+ // This prevents the dropdown from opening when the modal opens.
+ if (this.hasBeenFocused) {
+ this.loading = true;
+ this.retrieveUsers();
+ }
+
+ this.hasBeenFocused = true;
+ },
+ },
+ queryOptions: { exclude_internal: true, active: true },
+};
+</script>
+
+<template>
+ <gl-token-selector
+ v-model="selectedTokens"
+ :dropdown-items="users"
+ :loading="loading"
+ :allow-user-defined-tokens="false"
+ :hide-dropdown-with-no-items="hideDropdownWithNoItems"
+ :placeholder="placeholderText"
+ :aria-labelledby="ariaLabelledby"
+ @blur="handleBlur"
+ @text-input="handleTextInput"
+ @input="handleInput"
+ @focus="handleFocus"
+ >
+ <template #token-content="{ token }">
+ <gl-avatar v-if="token.avatar_url" :src="token.avatar_url" :size="16" />
+ {{ token.name }}
+ </template>
+
+ <template #dropdown-item-content="{ dropdownItem }">
+ <gl-avatar-labeled
+ :src="dropdownItem.avatar_url"
+ :size="32"
+ :label="dropdownItem.name"
+ :sub-label="dropdownItem.username"
+ />
+ </template>
+ </gl-token-selector>
+</template>
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
new file mode 100644
index 00000000000..1ff2125c292
--- /dev/null
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -0,0 +1 @@
+export const USER_SEARCH_DELAY = 200;
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index 92aa3187fc3..db957ecacfd 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -18,7 +18,6 @@ export default function initInviteMembersModal() {
props: {
...el.dataset,
accessLevels: JSON.parse(el.dataset.accessLevels),
- groupName: el.dataset.groupName.toUpperCase(),
},
}),
});