summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/invite_members
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/invite_members')
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue55
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue11
-rw-r--r--app/assets/javascripts/invite_members/components/project_select.vue94
-rw-r--r--app/assets/javascripts/invite_members/constants.js2
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js3
5 files changed, 87 insertions, 78 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 fa1aa6b0d88..607c888b85a 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -1,8 +1,7 @@
<script>
import {
GlAlert,
- GlDropdown,
- GlDropdownItem,
+ GlCollapsibleListbox,
GlLink,
GlSprintf,
GlFormCheckboxGroup,
@@ -13,6 +12,7 @@ import {
import { partition, isString, uniqueId, isEmpty } from 'lodash';
import InviteModalBase from 'ee_else_ce/invite_members/components/invite_modal_base.vue';
import Api from '~/api';
+import Tracking from '~/tracking';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { getParameterValues } from '~/lib/utils/url_utility';
@@ -22,6 +22,7 @@ import {
INVITE_MEMBERS_FOR_TASK,
MEMBER_MODAL_LABELS,
LEARN_GITLAB,
+ INVITE_MEMBER_MODAL_TRACKING_CATEGORY,
} from '../constants';
import eventHub from '../event_hub';
import { responseFromSuccess } from '../utils/response_message_parser';
@@ -40,8 +41,7 @@ export default {
components: {
GlAlert,
GlLink,
- GlDropdown,
- GlDropdownItem,
+ GlCollapsibleListbox,
GlSprintf,
GlFormCheckboxGroup,
GlButton,
@@ -52,6 +52,7 @@ export default {
ModalConfetti,
UserLimitNotification,
},
+ mixins: [Tracking.mixin({ category: INVITE_MEMBER_MODAL_TRACKING_CATEGORY })],
inject: ['newProjectPath'],
props: {
id: {
@@ -109,6 +110,11 @@ export default {
required: false,
default: () => ({}),
},
+ activeTrialDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
reloadPageOnSubmit: {
type: Boolean,
required: false,
@@ -124,6 +130,7 @@ export default {
invalidMembers: {},
selectedTasksToBeDone: [],
selectedTaskProject: this.projects[0],
+ selectedTaskProjectId: this.projects[0]?.id,
source: 'unknown',
mode: 'default',
// Kept in sync with "base"
@@ -131,6 +138,7 @@ export default {
errorsLimit: 2,
isErrorsSectionExpanded: false,
shouldShowEmptyInvitesAlert: false,
+ projectsForDropdown: this.projects.map((p) => ({ value: p.id, text: p.title, ...p })),
};
},
computed: {
@@ -263,11 +271,12 @@ export default {
usersToAddById.map((user) => user.id).join(','),
];
},
- openModal({ mode = 'default', source }) {
+ openModal({ mode = 'default', source = 'unknown' }) {
this.mode = mode;
this.source = source;
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
+ this.track('render', { label: this.source });
},
closeModal() {
this.$root.$emit(BV_HIDE_MODAL, this.modalId);
@@ -339,6 +348,12 @@ export default {
const tracking = new ExperimentTracking(INVITE_MEMBERS_FOR_TASK.name, { label, property });
tracking.event(INVITE_MEMBERS_FOR_TASK.submit);
},
+ onCancel() {
+ this.track('click_cancel', { label: this.source });
+ },
+ onClose() {
+ this.track('click_x', { label: this.source });
+ },
resetFields() {
this.clearValidation();
this.isLoading = false;
@@ -347,10 +362,12 @@ export default {
this.selectedTasksToBeDone = [];
[this.selectedTaskProject] = this.projects;
},
- changeSelectedTaskProject(project) {
- this.selectedTaskProject = project;
+ changeSelectedTaskProject(projectId) {
+ this.selectedTaskProject = this.projects.find((project) => project.id === projectId);
},
onInviteSuccess() {
+ this.track('invite_successful', { label: this.source });
+
if (this.reloadPageOnSubmit) {
reloadOnInvitationSuccess();
} else {
@@ -404,7 +421,10 @@ export default {
:new-users-to-invite="newUsersToInvite"
:root-group-id="rootId"
:users-limit-dataset="usersLimitDataset"
+ :active-trial-dataset="activeTrialDataset"
:full-path="fullPath"
+ @close="onClose"
+ @cancel="onCancel"
@reset="resetFields"
@submit="sendInvite"
@access-level="onAccessLevelUpdate"
@@ -513,23 +533,14 @@ export default {
<label class="gl-mt-5 gl-display-block">
{{ $options.labels.tasksProject.title }}
</label>
- <gl-dropdown
+ <gl-collapsible-listbox
+ v-model="selectedTaskProjectId"
+ :items="projectsForDropdown"
+ :block="true"
class="gl-w-half gl-xs-w-full"
- :text="selectedTaskProject.title"
data-testid="invite-members-modal-project-select"
- >
- <template v-for="project in projects">
- <gl-dropdown-item
- :key="project.id"
- active-class="is-active"
- is-check-item
- :is-checked="project.id === selectedTaskProject.id"
- @click="changeSelectedTaskProject(project)"
- >
- {{ project.title }}
- </gl-dropdown-item>
- </template>
- </gl-dropdown>
+ @select="changeSelectedTaskProject"
+ />
</template>
</template>
<gl-alert
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 2cbd681c67d..1e3b6093f0b 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -206,7 +206,7 @@ export default {
this.track('render', { category: 'default', label: ON_SHOW_TRACK_LABEL });
}
},
- onCloseModal(e) {
+ onCancel(e) {
if (this.preventCancelDefault) {
e.preventDefault();
} else {
@@ -225,6 +225,9 @@ export default {
expiresAt: this.selectedDate,
});
},
+ onClose() {
+ this.$emit('close');
+ },
},
HEADER_CLOSE_LABEL,
ACCESS_EXPIRE_DATE,
@@ -249,7 +252,8 @@ export default {
:action-cancel="actionCancel"
@shown="onShowModal"
@primary="onSubmit"
- @cancel="onCloseModal"
+ @cancel="onCancel"
+ @close="onClose"
@hidden="onReset"
>
<content-transition
@@ -267,11 +271,12 @@ export default {
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
+ <slot name="intro-text-after"></slot>
</p>
- <slot name="intro-text-after"></slot>
</div>
<slot name="alert"></slot>
+ <slot name="active-trial-alert"></slot>
<gl-form-group
:label="labelSearchField"
diff --git a/app/assets/javascripts/invite_members/components/project_select.vue b/app/assets/javascripts/invite_members/components/project_select.vue
index b7a3918813b..c1114c240b9 100644
--- a/app/assets/javascripts/invite_members/components/project_select.vue
+++ b/app/assets/javascripts/invite_members/components/project_select.vue
@@ -1,28 +1,23 @@
<script>
-import {
- GlAvatarLabeled,
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlSearchBoxByType,
-} from '@gitlab/ui';
+import { GlAvatarLabeled, GlCollapsibleListbox } from '@gitlab/ui';
import { debounce } from 'lodash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { getProjects } from '~/rest_api';
import { SEARCH_DELAY, GROUP_FILTERS } from '../constants';
+// We can have GlCollapsibleListbox dropdown panel with full
+// width once we implement
+// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2133
+// https://gitlab.com/gitlab-org/gitlab/-/issues/390411
export default {
name: 'ProjectSelect',
components: {
GlAvatarLabeled,
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlSearchBoxByType,
+ GlCollapsibleListbox,
},
model: {
- prop: 'selectedProject',
+ prop: 'selectedProjectId',
},
props: {
groupsFilter: {
@@ -41,18 +36,21 @@ export default {
return {
isFetching: false,
projects: [],
- selectedProject: {},
+ selectedProjectId: '',
searchTerm: '',
errorMessage: '',
};
},
computed: {
selectedProjectName() {
- return this.selectedProject.name || this.$options.i18n.dropdownText;
+ return this.selectedProject.nameWithNamespace || this.$options.i18n.dropdownText;
},
isFetchResultEmpty() {
return this.projects.length === 0 && !this.isFetching;
},
+ selectedProject() {
+ return this.projects.find((prj) => prj.id === this.selectedProjectId) || {};
+ },
},
watch: {
searchTerm() {
@@ -70,10 +68,14 @@ export default {
.then((response) => {
this.projects = response.data.map((project) => ({
...convertObjectPropsToCamelCase(project),
- name: project.name_with_namespace,
+ text: project.name_with_namespace,
+ value: project.id,
}));
})
.catch(() => {
+ // To be displayed in GlCollapsibleListbox once we implement
+ // https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2132
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/389974
this.errorMessage = this.$options.i18n.errorFetchingProjects;
})
.finally(() => {
@@ -83,9 +85,7 @@ export default {
fetchProjects() {
return getProjects(this.searchTerm, this.$options.defaultFetchOptions);
},
- selectProject(project) {
- this.selectedProject = project;
-
+ selectProject() {
this.$emit('input', this.selectedProject);
},
},
@@ -104,40 +104,28 @@ export default {
};
</script>
<template>
- <div>
- <gl-dropdown
- data-testid="project-select-dropdown"
- :text="selectedProjectName"
- toggle-class="gl-mb-2"
- block
- menu-class="gl-w-full!"
- >
- <gl-search-box-by-type
- v-model="searchTerm"
- :is-loading="isFetching"
- :placeholder="$options.i18n.searchPlaceholder"
- data-qa-selector="project_select_dropdown_search_field"
+ <gl-collapsible-listbox
+ v-model="selectedProjectId"
+ searchable
+ :items="projects"
+ :searching="isFetching"
+ :toggle-text="selectedProjectName"
+ :search-placeholder="$options.i18n.searchPlaceholder"
+ :no-results-text="$options.i18n.emptySearchResult"
+ data-testid="project-select-dropdown"
+ data-qa-selector="project_select_dropdown"
+ class="gl-collapsible-listbox-w-full"
+ @search="searchTerm = $event"
+ @select="selectProject"
+ >
+ <template #list-item="{ item }">
+ <gl-avatar-labeled
+ :label="item.text"
+ :src="item.avatarUrl"
+ :entity-id="item.id"
+ :entity-name="item.name"
+ :size="32"
/>
- <gl-dropdown-item
- v-for="project in projects"
- :key="project.id"
- :name="project.name"
- @click="selectProject(project)"
- >
- <gl-avatar-labeled
- :label="project.name"
- :src="project.avatarUrl"
- :entity-id="project.id"
- :entity-name="project.name"
- :size="32"
- />
- </gl-dropdown-item>
- <gl-dropdown-text v-if="errorMessage" data-testid="error-message">
- <span class="gl-text-gray-500">{{ errorMessage }}</span>
- </gl-dropdown-text>
- <gl-dropdown-text v-else-if="isFetchResultEmpty" data-testid="empty-result-message">
- <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span>
- </gl-dropdown-text>
- </gl-dropdown>
- </div>
+ </template>
+ </gl-collapsible-listbox>
</template>
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index edc0ebff083..ac0b708c55e 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -20,6 +20,7 @@ export const USERS_FILTER_ALL = 'all';
export const USERS_FILTER_SAML_PROVIDER_ID = 'saml_provider_id';
export const TRIGGER_ELEMENT_BUTTON = 'button';
export const TRIGGER_ELEMENT_SIDE_NAV = 'side-nav';
+export const INVITE_MEMBER_MODAL_TRACKING_CATEGORY = 'invite_members_modal';
export const TRIGGER_DEFAULT_QA_SELECTOR = 'invite_members_button';
export const MEMBERS_MODAL_DEFAULT_TITLE = s__('InviteMembersModal|Invite members');
export const MEMBERS_MODAL_CELEBRATE_TITLE = s__(
@@ -138,6 +139,7 @@ export const GROUP_MODAL_LABELS = {
export const LEARN_GITLAB = 'learn_gitlab';
export const ON_SHOW_TRACK_LABEL = 'over_limit_modal_viewed';
+export const ON_CELEBRATION_TRACK_LABEL = 'invite_celebration_modal';
export const INFO_ALERT_TITLE = s__(
'InviteMembersModal|Your top-level group %{namespaceName} is over the %{dashboardLimit} user limit.',
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 842ab07f368..4f539cd8756 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -41,6 +41,9 @@ export default (function initInviteMembersModal() {
usersLimitDataset: convertObjectPropsToCamelCase(
JSON.parse(el.dataset.usersLimitDataset || '{}'),
),
+ activeTrialDataset: convertObjectPropsToCamelCase(
+ JSON.parse(el.dataset.activeTrialDataset || '{}'),
+ ),
reloadPageOnSubmit: parseBoolean(el.dataset.reloadPageOnSubmit),
},
}),