diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-29 21:09:34 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-29 21:09:34 +0000 |
commit | 37a1347df2ee69411bc7ce08c3f2f695afe4a8a0 (patch) | |
tree | 269c320ab3e892fe690f81df3a712a9ad68b7b80 /app | |
parent | 2516f0d87bf4504cf0d626a0584b2eebe459749b (diff) | |
download | gitlab-ce-37a1347df2ee69411bc7ce08c3f2f695afe4a8a0.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/api.js | 31 | ||||
-rw-r--r-- | app/assets/javascripts/notifications/components/notifications_dropdown.vue | 169 | ||||
-rw-r--r-- | app/assets/javascripts/notifications/components/notifications_dropdown_item.vue | 42 | ||||
-rw-r--r-- | app/assets/javascripts/notifications/constants.js | 27 | ||||
-rw-r--r-- | app/assets/javascripts/notifications/index.js | 38 | ||||
-rw-r--r-- | app/assets/javascripts/pages/projects/show/index.js | 10 | ||||
-rw-r--r-- | app/controllers/projects_controller.rb | 4 | ||||
-rw-r--r-- | app/helpers/notifications_helper.rb | 9 | ||||
-rw-r--r-- | app/policies/project_policy.rb | 7 | ||||
-rw-r--r-- | app/services/users/approve_service.rb | 5 | ||||
-rw-r--r-- | app/services/users/reject_service.rb | 6 | ||||
-rw-r--r-- | app/views/projects/_home_panel.html.haml | 6 |
12 files changed, 352 insertions, 2 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index e8d433d36c9..2383e8747c3 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -83,6 +83,9 @@ const Api = { featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid', billableGroupMembersPath: '/api/:version/groups/:id/billable_members', containerRegistryDetailsPath: '/api/:version/registry/repositories/:id/', + projectNotificationSettingsPath: '/api/:version/projects/:id/notification_settings', + groupNotificationSettingsPath: '/api/:version/groups/:id/notification_settings', + notificationSettingsPath: '/api/:version/notification_settings', group(groupId, callback = () => {}) { const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); @@ -906,6 +909,34 @@ const Api = { return { data, headers }; }); }, + + async updateNotificationSettings(projectId, groupId, data = {}) { + let url = Api.buildUrl(this.notificationSettingsPath); + + if (projectId) { + url = Api.buildUrl(this.projectNotificationSettingsPath).replace(':id', projectId); + } else if (groupId) { + url = Api.buildUrl(this.groupNotificationSettingsPath).replace(':id', groupId); + } + + const result = await axios.put(url, data); + + return result; + }, + + async getNotificationSettings(projectId, groupId) { + let url = Api.buildUrl(this.notificationSettingsPath); + + if (projectId) { + url = Api.buildUrl(this.projectNotificationSettingsPath).replace(':id', projectId); + } else if (groupId) { + url = Api.buildUrl(this.groupNotificationSettingsPath).replace(':id', groupId); + } + + const result = await axios.get(url); + + return result; + }, }; export default Api; 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..cb524fbffa2 --- /dev/null +++ b/app/assets/javascripts/notifications/components/notifications_dropdown.vue @@ -0,0 +1,169 @@ +<script> +import { + GlButtonGroup, + GlButton, + GlDropdown, + GlDropdownDivider, + GlTooltipDirective, +} from '@gitlab/ui'; +import { sprintf } from '~/locale'; +import Api from '~/api'; +import NotificationsDropdownItem from './notifications_dropdown_item.vue'; +import { CUSTOM_LEVEL, i18n } from '../constants'; + +export default { + name: 'NotificationsDropdown', + components: { + GlButtonGroup, + GlButton, + GlDropdown, + GlDropdownDivider, + NotificationsDropdownItem, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + inject: { + containerClass: { + default: '', + }, + disabled: { + default: false, + }, + dropdownItems: { + default: [], + }, + buttonSize: { + default: 'medium', + }, + initialNotificationLevel: { + default: '', + }, + projectId: { + default: null, + }, + groupId: { + default: null, + }, + }, + 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'; + }, + 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; + } catch (error) { + this.$toast.show(this.$options.i18n.updateNotificationLevelErrorMessage, { type: 'error' }); + } finally { + this.isLoading = false; + } + }, + }, + customLevel: CUSTOM_LEVEL, + i18n, +}; +</script> + +<template> + <div :class="containerClass"> + <gl-button-group + v-if="isCustomNotification" + v-gl-tooltip="{ title: buttonTooltip }" + data-testid="notificationButton" + :size="buttonSize" + > + <gl-button :size="buttonSize" :icon="buttonIcon" :loading="isLoading" :disabled="disabled" /> + <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" + :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> + </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..f730ee59e84 --- /dev/null +++ b/app/assets/javascripts/notifications/constants.js @@ -0,0 +1,27 @@ +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.', + ), +}; diff --git a/app/assets/javascripts/notifications/index.js b/app/assets/javascripts/notifications/index.js new file mode 100644 index 00000000000..fe496c3aa3b --- /dev/null +++ b/app/assets/javascripts/notifications/index.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; +import { GlToast } from '@gitlab/ui'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import NotificationsDropdown from './components/notifications_dropdown.vue'; + +Vue.use(GlToast); + +export default () => { + const el = document.querySelector('.js-vue-notification-dropdown'); + + if (!el) return false; + + const { + containerClass, + buttonSize, + disabled, + dropdownItems, + notificationLevel, + projectId, + groupId, + } = el.dataset; + + return new Vue({ + el, + provide: { + containerClass, + buttonSize, + disabled: parseBoolean(disabled), + dropdownItems: JSON.parse(dropdownItems), + initialNotificationLevel: notificationLevel, + projectId, + groupId, + }, + render(h) { + return h(NotificationsDropdown); + }, + }); +}; diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index cc676b98e49..a2eb286076a 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -12,6 +12,7 @@ import notificationsDropdown from '../../../notifications_dropdown'; import { showLearnGitLabProjectPopover } from '~/onboarding_issues'; import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; +import initVueNotificationsDropdown from '~/notifications'; initReadMore(); new Star(); // eslint-disable-line no-new @@ -42,7 +43,14 @@ leaveByUrl('project'); showLearnGitLabProjectPopover(); -notificationsDropdown(); +if (gon.features?.vueNotificationDropdown) { + initVueNotificationsDropdown(); +} else { + notificationsDropdown(); +} + +initVueNotificationsDropdown(); + new ShortcutsNavigation(); // eslint-disable-line no-new initInviteMembersTrigger(); diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index eef54ee561f..64cb1c1ee52 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -31,6 +31,10 @@ class ProjectsController < Projects::ApplicationController # Project Export Rate Limit before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export] + before_action do + push_frontend_feature_flag(:vue_notification_dropdown, @project, default_enabled: :yaml) + end + before_action only: [:edit] do push_frontend_feature_flag(:approval_suggestions, @project, default_enabled: true) push_frontend_feature_flag(:allow_editing_commit_messages, @project) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 2b68d953431..729585be84a 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -125,4 +125,13 @@ module NotificationsHelper def can_read_project?(project) can?(current_user, :read_project, project) end + + def notification_dropdown_items(notification_setting) + NotificationSetting.levels.each_key.map do |level| + next if level == "custom" + next if level == "global" && notification_setting.source.nil? + + level + end.compact + end end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 03cb53f55be..f6d1b376b92 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -30,6 +30,9 @@ class ProjectPolicy < BasePolicy desc "User has maintainer access" condition(:maintainer) { team_access_level >= Gitlab::Access::MAINTAINER } + desc "User is a project bot" + condition(:project_bot) { user.project_bot? && team_member? } + desc "Project is public" condition(:public_project, scope: :subject, score: 0) { project.public? } @@ -616,10 +619,14 @@ class ProjectPolicy < BasePolicy prevent :read_project end + rule { project_bot }.enable :project_bot_access + rule { resource_access_token_available & can?(:admin_project) }.policy do enable :admin_resource_access_tokens end + rule { can?(:project_bot_access) }.prevent :admin_resource_access_tokens + rule { user_defined_variables_allowed | can?(:maintainer_access) }.policy do enable :set_pipeline_variables end diff --git a/app/services/users/approve_service.rb b/app/services/users/approve_service.rb index 76da4693415..fea7fc55d90 100644 --- a/app/services/users/approve_service.rb +++ b/app/services/users/approve_service.rb @@ -17,6 +17,7 @@ module Users user.accept_pending_invitations! if user.active_for_authentication? DeviseMailer.user_admin_approval(user).deliver_later + log_event(user) after_approve_hook(user) success(message: 'Success', http_status: :created) else @@ -39,6 +40,10 @@ module Users def approval_required?(user) user.blocked_pending_approval? end + + def log_event(user) + Gitlab::AppLogger.info(message: "User instance access request approved", user: "#{user.username}", email: "#{user.email}", approved_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}") + end end end diff --git a/app/services/users/reject_service.rb b/app/services/users/reject_service.rb index 1a103c77262..0e3eb3e5dde 100644 --- a/app/services/users/reject_service.rb +++ b/app/services/users/reject_service.rb @@ -16,6 +16,8 @@ module Users NotificationService.new.user_admin_rejection(user.name, user.email) + log_event(user) + success end @@ -30,6 +32,10 @@ module Users def after_reject_hook(user) # overridden by EE module end + + def log_event(user) + Gitlab::AppLogger.info(message: "User instance access request rejected", user: "#{user.username}", email: "#{user.email}", rejected_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}") + end end end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 1f826567002..9414e9b32d5 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -46,7 +46,11 @@ .project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end - if current_user .d-inline-flex - = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', dropdown_container_class: 'gl-mr-3', emails_disabled: emails_disabled + - if Feature.enabled?(:vue_notification_dropdown, @project, default_enabled: :yaml) + - if @notification_setting + .js-vue-notification-dropdown{ data: { button_size: "small", disabled: emails_disabled, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), project_id: @project.id, container_class: 'gl-mr-3 gl-mt-5 gl-vertical-align-top' } } + - else + = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', dropdown_container_class: 'gl-mr-3', emails_disabled: emails_disabled .count-buttons.d-inline-flex = render 'projects/buttons/star' |