diff options
Diffstat (limited to 'app/assets/javascripts/notifications/components')
3 files changed, 366 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> |