summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-29 21:09:34 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-29 21:09:34 +0000
commit37a1347df2ee69411bc7ce08c3f2f695afe4a8a0 (patch)
tree269c320ab3e892fe690f81df3a712a9ad68b7b80 /app
parent2516f0d87bf4504cf0d626a0584b2eebe459749b (diff)
downloadgitlab-ce-37a1347df2ee69411bc7ce08c3f2f695afe4a8a0.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js31
-rw-r--r--app/assets/javascripts/notifications/components/notifications_dropdown.vue169
-rw-r--r--app/assets/javascripts/notifications/components/notifications_dropdown_item.vue42
-rw-r--r--app/assets/javascripts/notifications/constants.js27
-rw-r--r--app/assets/javascripts/notifications/index.js38
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js10
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/helpers/notifications_helper.rb9
-rw-r--r--app/policies/project_policy.rb7
-rw-r--r--app/services/users/approve_service.rb5
-rw-r--r--app/services/users/reject_service.rb6
-rw-r--r--app/views/projects/_home_panel.html.haml6
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'