Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-19 10:33:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-19 10:33:21 +0300
commit36a59d088eca61b834191dacea009677a96c052f (patch)
treee4f33972dab5d8ef79e3944a9f403035fceea43f /app/assets/javascripts/invite_members
parenta1761f15ec2cae7c7f7bbda39a75494add0dfd6f (diff)
Add latest changes from gitlab-org/gitlab@15-0-stable-eev15.0.0-rc42
Diffstat (limited to 'app/assets/javascripts/invite_members')
-rw-r--r--app/assets/javascripts/invite_members/components/import_a_project_modal.vue2
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue26
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue171
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue4
-rw-r--r--app/assets/javascripts/invite_members/components/user_limit_notification.vue75
-rw-r--r--app/assets/javascripts/invite_members/constants.js32
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js9
7 files changed, 218 insertions, 101 deletions
diff --git a/app/assets/javascripts/invite_members/components/import_a_project_modal.vue b/app/assets/javascripts/invite_members/components/import_a_project_modal.vue
index d71468284ca..fb6c376cfe6 100644
--- a/app/assets/javascripts/invite_members/components/import_a_project_modal.vue
+++ b/app/assets/javascripts/invite_members/components/import_a_project_modal.vue
@@ -145,7 +145,7 @@ export default {
<gl-button
:disabled="importDisabled"
:loading="isLoading"
- variant="success"
+ variant="confirm"
data-testid="import-button"
@click="submitImport"
>{{ $options.i18n.modalPrimaryButton }}</gl-button
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 a9aa0e9b760..7857b9d86d2 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -88,6 +88,11 @@ export default {
type: Array,
required: true,
},
+ usersLimitDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
data() {
return {
@@ -146,6 +151,18 @@ export default {
isOnLearnGitlab() {
return this.source === LEARN_GITLAB;
},
+ reachedLimit() {
+ if (this.usersLimitDataset.freeUsersLimit && this.usersLimitDataset.membersCount) {
+ return this.usersLimitDataset.membersCount >= this.usersLimitDataset.freeUsersLimit;
+ }
+
+ return false;
+ },
+ formGroupDescription() {
+ return this.reachedLimit
+ ? this.$options.labels.placeHolderDisabled
+ : this.$options.labels.placeHolder;
+ },
},
mounted() {
eventHub.$on('openModal', (options) => {
@@ -274,12 +291,14 @@ export default {
:help-link="helpLink"
:label-intro-text="labelIntroText"
:label-search-field="$options.labels.searchField"
- :form-group-description="$options.labels.placeHolder"
+ :form-group-description="formGroupDescription"
:submit-disabled="inviteDisabled"
:invalid-feedback-message="invalidFeedbackMessage"
:is-loading="isLoading"
:new-users-to-invite="newUsersToInvite"
:root-group-id="rootId"
+ :reached-limit="reachedLimit"
+ :users-limit-dataset="usersLimitDataset"
@reset="resetFields"
@submit="sendInvite"
@access-level="onAccessLevelUpdate"
@@ -294,7 +313,10 @@ export default {
</template>
<template #user-limit-notification>
- <user-limit-notification />
+ <user-limit-notification
+ :reached-limit="reachedLimit"
+ :users-limit-dataset="usersLimitDataset"
+ />
</template>
<template #select="{ validationState, labelId }">
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 d9297614a7e..33d37b809c2 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -8,7 +8,9 @@ import {
GlLink,
GlSprintf,
GlFormInput,
+ GlIcon,
} from '@gitlab/ui';
+import Tracking from '~/tracking';
import { sprintf } from '~/locale';
import ContentTransition from '~/vue_shared/components/content_transition.vue';
import {
@@ -16,8 +18,13 @@ import {
ACCESS_EXPIRE_DATE,
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
+ INVITE_BUTTON_TEXT_DISABLED,
CANCEL_BUTTON_TEXT,
+ CANCEL_BUTTON_TEXT_DISABLED,
HEADER_CLOSE_LABEL,
+ ON_SHOW_TRACK_LABEL,
+ ON_CLOSE_TRACK_LABEL,
+ ON_SUBMIT_TRACK_LABEL,
} from '../constants';
const DEFAULT_SLOT = 'default';
@@ -41,8 +48,10 @@ export default {
GlDropdownItem,
GlSprintf,
GlFormInput,
+ GlIcon,
ContentTransition,
},
+ mixins: [Tracking.mixin()],
inheritAttrs: false,
props: {
modalTitle: {
@@ -122,6 +131,16 @@ export default {
required: false,
default: false,
},
+ reachedLimit: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ usersLimitDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
data() {
// Be sure to check out reset!
@@ -151,20 +170,27 @@ export default {
},
actionPrimary() {
return {
- text: this.submitButtonText,
+ text: this.reachedLimit ? INVITE_BUTTON_TEXT_DISABLED : this.submitButtonText,
attributes: {
variant: 'confirm',
- disabled: this.submitDisabled,
- loading: this.isLoading,
+ disabled: this.reachedLimit ? false : this.submitDisabled,
+ loading: this.reachedLimit ? false : this.isLoading,
'data-qa-selector': 'invite_button',
+ ...(this.reachedLimit && { href: this.usersLimitDataset.membersPath }),
},
};
},
actionCancel() {
+ if (this.reachedLimit && this.usersLimitDataset.userNamespace) return undefined;
+
return {
- text: this.cancelButtonText,
+ text: this.reachedLimit ? CANCEL_BUTTON_TEXT_DISABLED : this.cancelButtonText,
+ ...(this.reachedLimit && { attributes: { href: this.usersLimitDataset.purchasePath } }),
};
},
+ selectLabelClass() {
+ return `col-form-label ${this.reachedLimit ? 'gl-text-gray-500' : ''}`;
+ },
},
watch: {
selectedAccessLevel: {
@@ -183,15 +209,24 @@ export default {
this.$emit('reset');
},
+ onShowModal() {
+ if (this.reachedLimit) {
+ this.track('render', { category: 'default', label: ON_SHOW_TRACK_LABEL });
+ }
+ },
onCloseModal(e) {
- if (this.preventCancelDefault) {
+ if (this.preventCancelDefault || this.reachedLimit) {
e.preventDefault();
} else {
this.onReset();
this.$refs.modal.hide();
}
- this.$emit('cancel');
+ if (this.reachedLimit) {
+ this.track('click_button', { category: 'default', label: ON_CLOSE_TRACK_LABEL });
+ } else {
+ this.$emit('cancel');
+ }
},
changeSelectedItem(item) {
this.selectedAccessLevel = item;
@@ -200,10 +235,14 @@ export default {
// We never want to hide when submitting
e.preventDefault();
- this.$emit('submit', {
- accessLevel: this.selectedAccessLevel,
- expiresAt: this.selectedDate,
- });
+ if (this.reachedLimit) {
+ this.track('click_button', { category: 'default', label: ON_SUBMIT_TRACK_LABEL });
+ } else {
+ this.$emit('submit', {
+ accessLevel: this.selectedAccessLevel,
+ expiresAt: this.selectedDate,
+ });
+ }
},
},
HEADER_CLOSE_LABEL,
@@ -227,6 +266,7 @@ export default {
:header-close-label="$options.HEADER_CLOSE_LABEL"
:action-primary="actionPrimary"
:action-cancel="actionCancel"
+ @shown="onShowModal"
@primary="onSubmit"
@cancel="onCloseModal"
@hidden="onReset"
@@ -255,64 +295,73 @@ export default {
<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>
+ <template #description>
+ <gl-icon v-if="reachedLimit" name="lock" />
+ {{ formGroupDescription }}
+ </template>
+
+ <label :id="selectLabelId" :class="selectLabelClass">{{ labelSearchField }}</label>
+ <gl-form-input v-if="reachedLimit" data-testid="disabled-input" disabled />
+ <slot v-else name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
</gl-form-group>
- <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>
+ <template v-if="!reachedLimit">
+ <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
- <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>
+ <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>
+
<template v-for="{ key } in extraSlots" #[key]>
<slot :name="key"></slot>
</template>
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
index 0a191f6d406..30c9294344e 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -134,10 +134,10 @@ export default {
:hide-dropdown-with-no-items="hideDropdownWithNoItems"
:placeholder="placeholderText"
:aria-labelledby="ariaLabelledby"
- :text-input-attrs="{
+ :text-input-attrs="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
'data-testid': 'members-token-select-input',
'data-qa-selector': 'members_token_select_input',
- }"
+ } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@blur="handleBlur"
@text-input="handleTextInput"
@input="handleInput"
diff --git a/app/assets/javascripts/invite_members/components/user_limit_notification.vue b/app/assets/javascripts/invite_members/components/user_limit_notification.vue
index beef1aef8a1..ea5f4317d86 100644
--- a/app/assets/javascripts/invite_members/components/user_limit_notification.vue
+++ b/app/assets/javascripts/invite_members/components/user_limit_notification.vue
@@ -1,35 +1,51 @@
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { s__, n__, sprintf } from '~/locale';
+import { n__, sprintf } from '~/locale';
-const CLOSE_TO_LIMIT_COUNT = 2;
-
-const WARNING_ALERT_TITLE = s__(
- 'InviteMembersModal|You only have space for %{count} more %{members} in %{name}',
-);
-
-const DANGER_ALERT_TITLE = s__(
- "InviteMembersModal|You've reached your %{count} %{members} limit for %{name}",
-);
-
-const CLOSE_TO_LIMIT_MESSAGE = s__(
- 'InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier.',
-);
+import {
+ WARNING_ALERT_TITLE,
+ DANGER_ALERT_TITLE,
+ REACHED_LIMIT_MESSAGE,
+ REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE,
+ CLOSE_TO_LIMIT_MESSAGE,
+} from '../constants';
-const REACHED_LIMIT_MESSAGE = s__(
- 'InviteMembersModal|New members will be unable to participate. You can manage your members by removing ones you no longer need.',
-).concat(' ', CLOSE_TO_LIMIT_MESSAGE);
+const CLOSE_TO_LIMIT_COUNT = 2;
export default {
name: 'UserLimitNotification',
components: { GlAlert, GlSprintf, GlLink },
- inject: ['name', 'newTrialRegistrationPath', 'purchasePath', 'freeUsersLimit', 'membersCount'],
+ inject: ['name'],
+ props: {
+ reachedLimit: {
+ type: Boolean,
+ required: true,
+ },
+ usersLimitDataset: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
computed: {
- reachedLimit() {
- return this.isLimit();
+ freeUsersLimit() {
+ return this.usersLimitDataset.freeUsersLimit;
+ },
+ membersCount() {
+ return this.usersLimitDataset.membersCount;
+ },
+ newTrialRegistrationPath() {
+ return this.usersLimitDataset.newTrialRegistrationPath;
+ },
+ purchasePath() {
+ return this.usersLimitDataset.purchasePath;
},
closeToLimit() {
- return this.isLimit(CLOSE_TO_LIMIT_COUNT);
+ if (this.freeUsersLimit && this.membersCount) {
+ return this.membersCount >= this.freeUsersLimit - CLOSE_TO_LIMIT_COUNT;
+ }
+
+ return false;
},
warningAlertTitle() {
return sprintf(WARNING_ALERT_TITLE, {
@@ -51,28 +67,29 @@ export default {
title() {
return this.reachedLimit ? this.dangerAlertTitle : this.warningAlertTitle;
},
+ reachedLimitMessage() {
+ if (this.usersLimitDataset.userNamespace) {
+ return this.$options.i18n.reachedLimitMessage;
+ }
+
+ return this.$options.i18n.reachedLimitUpgradeSuggestionMessage;
+ },
message() {
if (this.reachedLimit) {
- return this.$options.i18n.reachedLimitMessage;
+ return this.reachedLimitMessage;
}
return this.$options.i18n.closeToLimitMessage;
},
},
methods: {
- isLimit(deviation = 0) {
- if (this.freeUsersLimit && this.membersCount) {
- return this.membersCount >= this.freeUsersLimit - deviation;
- }
-
- return false;
- },
pluralMembers(count) {
return n__('member', 'members', count);
},
},
i18n: {
reachedLimitMessage: REACHED_LIMIT_MESSAGE,
+ reachedLimitUpgradeSuggestionMessage: REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE,
closeToLimitMessage: CLOSE_TO_LIMIT_MESSAGE,
},
};
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index 3cd0bfc0181..928f79f1c8d 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -35,8 +35,11 @@ export const MEMBERS_TO_PROJECT_DEFAULT_INTRO_TEXT = s__(
export const MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT = s__(
"InviteMembersModal|Congratulations on creating your project, you're almost there!",
);
-export const MEMBERS_SEARCH_FIELD = s__('InviteMembersModal|GitLab member or email address');
+export const MEMBERS_SEARCH_FIELD = s__('InviteMembersModal|Username or email address');
export const MEMBERS_PLACEHOLDER = s__('InviteMembersModal|Select members or type email addresses');
+export const MEMBERS_PLACEHOLDER_DISABLED = s__(
+ 'InviteMembersModal|This feature is disabled until this group has space for more members.',
+);
export const MEMBERS_TASKS_TO_BE_DONE_TITLE = s__(
'InviteMembersModal|Create issues for your new team member to work on (optional)',
);
@@ -66,7 +69,9 @@ export const READ_MORE_TEXT = s__(
`InviteMembersModal|%{linkStart}Read more%{linkEnd} about role permissions`,
);
export const INVITE_BUTTON_TEXT = s__('InviteMembersModal|Invite');
+export const INVITE_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Manage members');
export const CANCEL_BUTTON_TEXT = s__('InviteMembersModal|Cancel');
+export const CANCEL_BUTTON_TEXT_DISABLED = s__('InviteMembersModal|Explore paid plans');
export const HEADER_CLOSE_LABEL = s__('InviteMembersModal|Close invite team members');
export const MEMBER_MODAL_LABELS = {
@@ -94,6 +99,7 @@ export const MEMBER_MODAL_LABELS = {
},
searchField: MEMBERS_SEARCH_FIELD,
placeHolder: MEMBERS_PLACEHOLDER,
+ placeHolderDisabled: MEMBERS_PLACEHOLDER_DISABLED,
tasksToBeDone: {
title: MEMBERS_TASKS_TO_BE_DONE_TITLE,
noProjects: MEMBERS_TASKS_TO_BE_DONE_NO_PROJECTS,
@@ -118,3 +124,27 @@ export const GROUP_MODAL_LABELS = {
};
export const LEARN_GITLAB = 'learn_gitlab';
+export const ON_SHOW_TRACK_LABEL = 'locked_modal_viewed';
+export const ON_CLOSE_TRACK_LABEL = 'explore_paid_plans_clicked';
+export const ON_SUBMIT_TRACK_LABEL = 'manage_members_clicked';
+
+export const WARNING_ALERT_TITLE = s__(
+ 'InviteMembersModal|You only have space for %{count} more %{members} in %{name}',
+);
+export const DANGER_ALERT_TITLE = s__(
+ "InviteMembersModal|You've reached your %{count} %{members} limit for %{name}",
+);
+
+export const REACHED_LIMIT_MESSAGE = s__(
+ 'InviteMembersModal|You cannot add more members, but you can remove members who no longer need access.',
+);
+
+export const REACHED_LIMIT_UPGRADE_SUGGESTION_MESSAGE = REACHED_LIMIT_MESSAGE.concat(
+ s__(
+ 'InviteMembersModal| To get more members and access to additional paid features, an owner of this namespace can start a trial or upgrade to a paid tier.',
+ ),
+);
+
+export const CLOSE_TO_LIMIT_MESSAGE = s__(
+ 'InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier.',
+);
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 958121ad735..a4be3f205a3 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -1,7 +1,7 @@
import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
Vue.use(GlToast);
@@ -26,10 +26,6 @@ export default (function initInviteMembersModal() {
provide: {
name: el.dataset.name,
newProjectPath: el.dataset.newProjectPath,
- newTrialRegistrationPath: el.dataset.newTrialRegistrationPath,
- purchasePath: el.dataset.purchasePath,
- freeUsersLimit: el.dataset.freeUsersLimit && parseInt(el.dataset.freeUsersLimit, 10),
- membersCount: el.dataset.membersCount && parseInt(el.dataset.membersCount, 10),
},
render: (createElement) =>
createElement(InviteMembersModal, {
@@ -42,6 +38,9 @@ export default (function initInviteMembersModal() {
projects: JSON.parse(el.dataset.projects || '[]'),
usersFilter: el.dataset.usersFilter,
filterId: parseInt(el.dataset.filterId, 10),
+ usersLimitDataset: convertObjectPropsToCamelCase(
+ JSON.parse(el.dataset.usersLimitDataset || '{}'),
+ ),
},
}),
});