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>2021-02-18 13:34:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 13:34:06 +0300
commit859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch)
treed7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/assets/javascripts/notifications
parent446d496a6d000c73a304be52587cd9bbc7493136 (diff)
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/notifications')
-rw-r--r--app/assets/javascripts/notifications/components/custom_notifications_modal.vue128
-rw-r--r--app/assets/javascripts/notifications/components/notifications_dropdown.vue196
-rw-r--r--app/assets/javascripts/notifications/components/notifications_dropdown_item.vue42
-rw-r--r--app/assets/javascripts/notifications/constants.js58
-rw-r--r--app/assets/javascripts/notifications/index.js44
5 files changed, 468 insertions, 0 deletions
diff --git a/app/assets/javascripts/notifications/components/custom_notifications_modal.vue b/app/assets/javascripts/notifications/components/custom_notifications_modal.vue
new file mode 100644
index 00000000000..0f628897e17
--- /dev/null
+++ b/app/assets/javascripts/notifications/components/custom_notifications_modal.vue
@@ -0,0 +1,128 @@
+<script>
+import { GlModal, GlSprintf, GlLink, GlLoadingIcon, GlFormGroup, GlFormCheckbox } from '@gitlab/ui';
+import Api from '~/api';
+import { i18n } from '../constants';
+
+export default {
+ name: 'CustomNotificationsModal',
+ components: {
+ GlModal,
+ GlSprintf,
+ GlLink,
+ GlLoadingIcon,
+ GlFormGroup,
+ GlFormCheckbox,
+ },
+ inject: {
+ projectId: {
+ default: null,
+ },
+ groupId: {
+ default: null,
+ },
+ helpPagePath: {
+ default: '',
+ },
+ },
+ props: {
+ modalId: {
+ type: String,
+ required: false,
+ default: 'custom-notifications-modal',
+ },
+ },
+ data() {
+ return {
+ isLoading: false,
+ events: [],
+ };
+ },
+ methods: {
+ open() {
+ this.$refs.modal.show();
+ },
+ buildEvents(events) {
+ return Object.keys(events).map((key) => ({
+ id: key,
+ enabled: Boolean(events[key]),
+ name: this.$options.i18n.eventNames[key] || '',
+ loading: false,
+ }));
+ },
+ async onOpen() {
+ if (!this.events.length) {
+ await this.loadNotificationSettings();
+ }
+ },
+ async loadNotificationSettings() {
+ this.isLoading = true;
+
+ try {
+ const {
+ data: { events },
+ } = await Api.getNotificationSettings(this.projectId, this.groupId);
+
+ this.events = this.buildEvents(events);
+ } catch (error) {
+ this.$toast.show(this.$options.i18n.loadNotificationLevelErrorMessage, { type: 'error' });
+ } finally {
+ this.isLoading = false;
+ }
+ },
+ async updateEvent(isEnabled, event) {
+ const index = this.events.findIndex((e) => e.id === event.id);
+
+ // update loading state for the given event
+ this.$set(this.events, index, { ...this.events[index], loading: true });
+
+ try {
+ const {
+ data: { events },
+ } = await Api.updateNotificationSettings(this.projectId, this.groupId, {
+ [event.id]: isEnabled,
+ });
+
+ this.events = this.buildEvents(events);
+ } catch (error) {
+ this.$toast.show(this.$options.i18n.updateNotificationLevelErrorMessage, { type: 'error' });
+ }
+ },
+ },
+ i18n,
+};
+</script>
+
+<template>
+ <gl-modal
+ ref="modal"
+ :modal-id="modalId"
+ :title="$options.i18n.customNotificationsModal.title"
+ @show="onOpen"
+ >
+ <div class="container-fluid">
+ <div class="row">
+ <div class="col-lg-4">
+ <h4 class="gl-mt-0" data-testid="modalBodyTitle">
+ {{ $options.i18n.customNotificationsModal.bodyTitle }}
+ </h4>
+ <gl-sprintf :message="$options.i18n.customNotificationsModal.bodyMessage">
+ <template #notificationLink="{ content }">
+ <gl-link :href="helpPagePath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ <div class="col-lg-8">
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3" />
+ <template v-else>
+ <gl-form-group v-for="event in events" :key="event.id">
+ <gl-form-checkbox v-model="event.enabled" @change="updateEvent($event, event)">
+ <strong>{{ event.name }}</strong
+ ><gl-loading-icon v-if="event.loading" :inline="true" class="gl-ml-2" />
+ </gl-form-checkbox>
+ </gl-form-group>
+ </template>
+ </div>
+ </div>
+ </div>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/notifications/components/notifications_dropdown.vue b/app/assets/javascripts/notifications/components/notifications_dropdown.vue
new file mode 100644
index 00000000000..e4cedfdb810
--- /dev/null
+++ b/app/assets/javascripts/notifications/components/notifications_dropdown.vue
@@ -0,0 +1,196 @@
+<script>
+import {
+ GlButtonGroup,
+ GlButton,
+ GlDropdown,
+ GlDropdownDivider,
+ GlTooltipDirective,
+ GlModalDirective,
+} from '@gitlab/ui';
+import Api from '~/api';
+import { sprintf } from '~/locale';
+import { CUSTOM_LEVEL, i18n } from '../constants';
+import CustomNotificationsModal from './custom_notifications_modal.vue';
+import NotificationsDropdownItem from './notifications_dropdown_item.vue';
+
+export default {
+ name: 'NotificationsDropdown',
+ components: {
+ GlButtonGroup,
+ GlButton,
+ GlDropdown,
+ GlDropdownDivider,
+ NotificationsDropdownItem,
+ CustomNotificationsModal,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ 'gl-modal': GlModalDirective,
+ },
+ inject: {
+ containerClass: {
+ default: '',
+ },
+ disabled: {
+ default: false,
+ },
+ dropdownItems: {
+ default: [],
+ },
+ buttonSize: {
+ default: 'medium',
+ },
+ initialNotificationLevel: {
+ default: '',
+ },
+ projectId: {
+ default: null,
+ },
+ groupId: {
+ default: null,
+ },
+ showLabel: {
+ default: false,
+ },
+ },
+ data() {
+ return {
+ selectedNotificationLevel: this.initialNotificationLevel,
+ isLoading: false,
+ };
+ },
+ computed: {
+ notificationLevels() {
+ return this.dropdownItems.map((level) => ({
+ level,
+ title: this.$options.i18n.notificationTitles[level] || '',
+ description: this.$options.i18n.notificationDescriptions[level] || '',
+ }));
+ },
+ isCustomNotification() {
+ return this.selectedNotificationLevel === CUSTOM_LEVEL;
+ },
+ buttonIcon() {
+ if (this.isLoading) {
+ return null;
+ }
+
+ return this.selectedNotificationLevel === 'disabled' ? 'notifications-off' : 'notifications';
+ },
+ buttonText() {
+ return this.showLabel
+ ? this.$options.i18n.notificationTitles[this.selectedNotificationLevel]
+ : null;
+ },
+ buttonTooltip() {
+ const notificationTitle =
+ this.$options.i18n.notificationTitles[this.selectedNotificationLevel] ||
+ this.selectedNotificationLevel;
+
+ return this.disabled
+ ? this.$options.i18n.notificationDescriptions.owner_disabled
+ : sprintf(this.$options.i18n.notificationTooltipTitle, {
+ notification_title: notificationTitle,
+ });
+ },
+ },
+ methods: {
+ selectItem(level) {
+ if (level !== this.selectedNotificationLevel) {
+ this.updateNotificationLevel(level);
+ }
+ },
+ async updateNotificationLevel(level) {
+ this.isLoading = true;
+
+ try {
+ await Api.updateNotificationSettings(this.projectId, this.groupId, { level });
+ this.selectedNotificationLevel = level;
+
+ if (level === CUSTOM_LEVEL) {
+ this.$refs.customNotificationsModal.open();
+ }
+ } catch (error) {
+ this.$toast.show(this.$options.i18n.updateNotificationLevelErrorMessage, { type: 'error' });
+ } finally {
+ this.isLoading = false;
+ }
+ },
+ },
+ customLevel: CUSTOM_LEVEL,
+ i18n,
+ modalId: 'custom-notifications-modal',
+};
+</script>
+
+<template>
+ <div :class="containerClass">
+ <gl-button-group
+ v-if="isCustomNotification"
+ v-gl-tooltip="{ title: buttonTooltip }"
+ data-testid="notificationButton"
+ :size="buttonSize"
+ >
+ <gl-button
+ v-gl-modal="$options.modalId"
+ :size="buttonSize"
+ :icon="buttonIcon"
+ :loading="isLoading"
+ :disabled="disabled"
+ >
+ <template v-if="buttonText">{{ buttonText }}</template>
+ </gl-button>
+ <gl-dropdown :size="buttonSize" :disabled="disabled">
+ <notifications-dropdown-item
+ v-for="item in notificationLevels"
+ :key="item.level"
+ :level="item.level"
+ :title="item.title"
+ :description="item.description"
+ :notification-level="selectedNotificationLevel"
+ @item-selected="selectItem"
+ />
+ <gl-dropdown-divider />
+ <notifications-dropdown-item
+ :key="$options.customLevel"
+ :level="$options.customLevel"
+ :title="$options.i18n.notificationTitles.custom"
+ :description="$options.i18n.notificationDescriptions.custom"
+ :notification-level="selectedNotificationLevel"
+ @item-selected="selectItem"
+ />
+ </gl-dropdown>
+ </gl-button-group>
+
+ <gl-dropdown
+ v-else
+ v-gl-tooltip="{ title: buttonTooltip }"
+ data-testid="notificationButton"
+ :text="buttonText"
+ :icon="buttonIcon"
+ :loading="isLoading"
+ :size="buttonSize"
+ :disabled="disabled"
+ >
+ <notifications-dropdown-item
+ v-for="item in notificationLevels"
+ :key="item.level"
+ :level="item.level"
+ :title="item.title"
+ :description="item.description"
+ :notification-level="selectedNotificationLevel"
+ @item-selected="selectItem"
+ />
+ <gl-dropdown-divider />
+ <notifications-dropdown-item
+ :key="$options.customLevel"
+ :level="$options.customLevel"
+ :title="$options.i18n.notificationTitles.custom"
+ :description="$options.i18n.notificationDescriptions.custom"
+ :notification-level="selectedNotificationLevel"
+ @item-selected="selectItem"
+ />
+ </gl-dropdown>
+ <custom-notifications-modal ref="customNotificationsModal" :modal-id="$options.modalId" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/notifications/components/notifications_dropdown_item.vue b/app/assets/javascripts/notifications/components/notifications_dropdown_item.vue
new file mode 100644
index 00000000000..73bb9c1b36f
--- /dev/null
+++ b/app/assets/javascripts/notifications/components/notifications_dropdown_item.vue
@@ -0,0 +1,42 @@
+<script>
+import { GlDropdownItem } from '@gitlab/ui';
+
+export default {
+ name: 'NotificationsDropdownItem',
+ components: {
+ GlDropdownItem,
+ },
+ props: {
+ level: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ description: {
+ type: String,
+ required: true,
+ },
+ notificationLevel: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ isActive() {
+ return this.notificationLevel === this.level;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-dropdown-item is-check-item :is-checked="isActive" @click="$emit('item-selected', level)">
+ <div class="gl-display-flex gl-flex-direction-column">
+ <span class="gl-font-weight-bold">{{ title }}</span>
+ <span class="gl-text-gray-500">{{ description }}</span>
+ </div>
+ </gl-dropdown-item>
+</template>
diff --git a/app/assets/javascripts/notifications/constants.js b/app/assets/javascripts/notifications/constants.js
new file mode 100644
index 00000000000..07c569a0293
--- /dev/null
+++ b/app/assets/javascripts/notifications/constants.js
@@ -0,0 +1,58 @@
+import { __, s__ } from '~/locale';
+
+export const CUSTOM_LEVEL = 'custom';
+
+export const i18n = {
+ notificationTitles: {
+ participating: s__('NotificationLevel|Participate'),
+ mention: s__('NotificationLevel|On mention'),
+ watch: s__('NotificationLevel|Watch'),
+ global: s__('NotificationLevel|Global'),
+ disabled: s__('NotificationLevel|Disabled'),
+ custom: s__('NotificationLevel|Custom'),
+ },
+ notificationTooltipTitle: __('Notification setting - %{notification_title}'),
+ notificationDescriptions: {
+ participating: __('You will only receive notifications for threads you have participated in'),
+ mention: __('You will receive notifications only for comments in which you were @mentioned'),
+ watch: __('You will receive notifications for any activity'),
+ disabled: __('You will not get any notifications via email'),
+ global: __('Use your global notification setting'),
+ custom: __('You will only receive notifications for the events you choose'),
+ owner_disabled: __('Notifications have been disabled by the project or group owner'),
+ },
+ updateNotificationLevelErrorMessage: __(
+ 'An error occured while updating the notification settings. Please try again.',
+ ),
+ loadNotificationLevelErrorMessage: __(
+ 'An error occured while loading the notification settings. Please try again.',
+ ),
+ customNotificationsModal: {
+ title: __('Custom notification events'),
+ bodyTitle: __('Notification events'),
+ bodyMessage: __(
+ 'Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notificationLinkStart} notification emails%{notificationLinkEnd}.',
+ ),
+ },
+ eventNames: {
+ change_reviewer_merge_request: s__('NotificationEvent|Change reviewer merge request'),
+ close_issue: s__('NotificationEvent|Close issue'),
+ close_merge_request: s__('NotificationEvent|Close merge request'),
+ failed_pipeline: s__('NotificationEvent|Failed pipeline'),
+ fixed_pipeline: s__('NotificationEvent|Fixed pipeline'),
+ issue_due: s__('NotificationEvent|Issue due'),
+ merge_merge_request: s__('NotificationEvent|Merge merge request'),
+ moved_project: s__('NotificationEvent|Moved project'),
+ new_epic: s__('NotificationEvent|New epic'),
+ new_issue: s__('NotificationEvent|New issue'),
+ new_merge_request: s__('NotificationEvent|New merge request'),
+ new_note: s__('NotificationEvent|New note'),
+ new_release: s__('NotificationEvent|New release'),
+ push_to_merge_request: s__('NotificationEvent|Push to merge request'),
+ reassign_issue: s__('NotificationEvent|Reassign issue'),
+ reassign_merge_request: s__('NotificationEvent|Reassign merge request'),
+ reopen_issue: s__('NotificationEvent|Reopen issue'),
+ reopen_merge_request: s__('NotificationEvent|Reopen merge request'),
+ success_pipeline: s__('NotificationEvent|Successful pipeline'),
+ },
+};
diff --git a/app/assets/javascripts/notifications/index.js b/app/assets/javascripts/notifications/index.js
new file mode 100644
index 00000000000..d60a368703c
--- /dev/null
+++ b/app/assets/javascripts/notifications/index.js
@@ -0,0 +1,44 @@
+import { GlToast } from '@gitlab/ui';
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import NotificationsDropdown from './components/notifications_dropdown.vue';
+
+Vue.use(GlToast);
+
+export default () => {
+ const containers = document.querySelectorAll('.js-vue-notification-dropdown');
+
+ if (!containers.length) return false;
+
+ return containers.forEach((el) => {
+ const {
+ containerClass,
+ buttonSize,
+ disabled,
+ dropdownItems,
+ notificationLevel,
+ helpPagePath,
+ projectId,
+ groupId,
+ showLabel,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ provide: {
+ containerClass,
+ buttonSize,
+ disabled: parseBoolean(disabled),
+ dropdownItems: JSON.parse(dropdownItems),
+ initialNotificationLevel: notificationLevel,
+ helpPagePath,
+ projectId,
+ groupId,
+ showLabel: parseBoolean(showLabel),
+ },
+ render(h) {
+ return h(NotificationsDropdown);
+ },
+ });
+ });
+};