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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-03 03:09:53 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-03 03:09:53 +0300
commit1502c20d04c7ff8d719175c76b0a2507ab390172 (patch)
treedc01bfe0877bd93e7047db28dd972c7d597a527b /app
parentf96f2720d1b21b76eadedc54fdea67cb70e98d94 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue12
-rw-r--r--app/assets/javascripts/integrations/edit/store/actions.js21
-rw-r--r--app/assets/javascripts/integrations/edit/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/integrations/edit/store/mutations.js6
-rw-r--r--app/assets/javascripts/pipelines/components/graph/utils.js34
-rw-r--r--app/assets/javascripts/pipelines/components/unwrapping_utils.js36
-rw-r--r--app/controllers/admin/users_controller.rb10
-rw-r--r--app/controllers/concerns/integrations_actions.rb6
-rw-r--r--app/controllers/invites_controller.rb11
-rw-r--r--app/graphql/types/merge_request_connection_type.rb15
-rw-r--r--app/graphql/types/merge_request_type.rb2
-rw-r--r--app/helpers/services_helper.rb16
-rw-r--r--app/mailers/emails/members.rb6
-rw-r--r--app/mailers/emails/profile.rb8
-rw-r--r--app/models/merge_request.rb7
-rw-r--r--app/models/merge_request/metrics.rb5
-rw-r--r--app/policies/global_policy.rb1
-rw-r--r--app/services/members/invitation_reminder_email_service.rb2
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/services/users/reject_service.rb28
-rw-r--r--app/views/admin/users/_reject_pending_user.html.haml7
-rw-r--r--app/views/admin/users/_user.html.haml5
-rw-r--r--app/views/admin/users/_user_reject_effects.html.haml10
-rw-r--r--app/views/admin/users/show.html.haml88
-rw-r--r--app/views/notify/user_admin_rejection_email.html.haml5
-rw-r--r--app/views/notify/user_admin_rejection_email.text.erb6
-rw-r--r--app/views/shared/_service_settings.html.haml4
-rw-r--r--app/workers/member_invitation_reminder_emails_worker.rb2
28 files changed, 266 insertions, 94 deletions
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 32366c5068f..ac8a64d5f3b 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -61,7 +61,13 @@ export default {
},
},
methods: {
- ...mapActions(['setOverride', 'setIsSaving', 'setIsTesting', 'setIsResetting']),
+ ...mapActions([
+ 'setOverride',
+ 'setIsSaving',
+ 'setIsTesting',
+ 'setIsResetting',
+ 'fetchResetIntegration',
+ ]),
onSaveClick() {
this.setIsSaving(true);
eventHub.$emit('saveIntegration');
@@ -70,7 +76,9 @@ export default {
this.setIsTesting(true);
eventHub.$emit('testIntegration');
},
- onResetClick() {},
+ onResetClick() {
+ this.fetchResetIntegration();
+ },
},
};
</script>
diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js
index 097304be242..421917b720a 100644
--- a/app/assets/javascripts/integrations/edit/store/actions.js
+++ b/app/assets/javascripts/integrations/edit/store/actions.js
@@ -1,3 +1,5 @@
+import axios from 'axios';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
import * as types from './mutation_types';
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
@@ -5,3 +7,22 @@ export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING,
export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting);
export const setIsResetting = ({ commit }, isResetting) =>
commit(types.SET_IS_RESETTING, isResetting);
+
+export const requestResetIntegration = ({ commit }) => {
+ commit(types.REQUEST_RESET_INTEGRATION);
+};
+export const receiveResetIntegrationSuccess = () => {
+ refreshCurrentPage();
+};
+export const receiveResetIntegrationError = ({ commit }) => {
+ commit(types.RECEIVE_RESET_INTEGRATION_ERROR);
+};
+
+export const fetchResetIntegration = ({ dispatch, getters }) => {
+ dispatch('requestResetIntegration');
+
+ return axios
+ .post(getters.propsSource.resetPath, { params: { format: 'json' } })
+ .then(() => dispatch('receiveResetIntegrationSuccess'))
+ .catch(() => dispatch('receiveResetIntegrationError'));
+};
diff --git a/app/assets/javascripts/integrations/edit/store/mutation_types.js b/app/assets/javascripts/integrations/edit/store/mutation_types.js
index 2a84408f658..54928148b22 100644
--- a/app/assets/javascripts/integrations/edit/store/mutation_types.js
+++ b/app/assets/javascripts/integrations/edit/store/mutation_types.js
@@ -2,3 +2,6 @@ export const SET_OVERRIDE = 'SET_OVERRIDE';
export const SET_IS_SAVING = 'SET_IS_SAVING';
export const SET_IS_TESTING = 'SET_IS_TESTING';
export const SET_IS_RESETTING = 'SET_IS_RESETTING';
+
+export const REQUEST_RESET_INTEGRATION = 'REQUEST_RESET_INTEGRATION';
+export const RECEIVE_RESET_INTEGRATION_ERROR = 'RECEIVE_RESET_INTEGRATION_ERROR';
diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js
index 07e3e25ccf0..826757e665b 100644
--- a/app/assets/javascripts/integrations/edit/store/mutations.js
+++ b/app/assets/javascripts/integrations/edit/store/mutations.js
@@ -13,4 +13,10 @@ export default {
[types.SET_IS_RESETTING](state, isResetting) {
state.isResetting = isResetting;
},
+ [types.REQUEST_RESET_INTEGRATION](state) {
+ state.isResetting = true;
+ },
+ [types.RECEIVE_RESET_INTEGRATION_ERROR](state) {
+ state.isResetting = false;
+ },
};
diff --git a/app/assets/javascripts/pipelines/components/graph/utils.js b/app/assets/javascripts/pipelines/components/graph/utils.js
index 698bade19fe..df3615772ce 100644
--- a/app/assets/javascripts/pipelines/components/graph/utils.js
+++ b/app/assets/javascripts/pipelines/components/graph/utils.js
@@ -1,3 +1,9 @@
+import { unwrapStagesWithNeeds } from '../unwrapping_utils';
+
+const addMulti = (mainId, pipeline) => {
+ return { ...pipeline, multiproject: mainId !== pipeline.id };
+};
+
const unwrapPipelineData = (mainPipelineId, data) => {
if (!data?.project?.pipeline) {
return null;
@@ -10,35 +16,13 @@ const unwrapPipelineData = (mainPipelineId, data) => {
stages: { nodes: stages },
} = data.project.pipeline;
- const unwrappedNestedGroups = stages.map(stage => {
- const {
- groups: { nodes: groups },
- } = stage;
- return { ...stage, groups };
- });
-
- const nodes = unwrappedNestedGroups.map(({ name, status, groups }) => {
- const groupsWithJobs = groups.map(group => {
- const jobs = group.jobs.nodes.map(job => {
- const { needs } = job;
- return { ...job, needs: needs.nodes.map(need => need.name) };
- });
-
- return { ...group, jobs };
- });
-
- return { name, status, groups: groupsWithJobs };
- });
-
- const addMulti = pipeline => {
- return { ...pipeline, multiproject: mainPipelineId !== pipeline.id };
- };
+ const nodes = unwrapStagesWithNeeds(stages);
return {
id,
stages: nodes,
- upstream: upstream ? [upstream].map(addMulti) : [],
- downstream: downstream ? downstream.map(addMulti) : [],
+ upstream: upstream ? [upstream].map(addMulti.bind(null, mainPipelineId)) : [],
+ downstream: downstream ? downstream.map(addMulti.bind(null, mainPipelineId)) : [],
};
};
diff --git a/app/assets/javascripts/pipelines/components/unwrapping_utils.js b/app/assets/javascripts/pipelines/components/unwrapping_utils.js
new file mode 100644
index 00000000000..99934cd5014
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/unwrapping_utils.js
@@ -0,0 +1,36 @@
+const unwrapGroups = stages => {
+ return stages.map(stage => {
+ const {
+ groups: { nodes: groups },
+ } = stage;
+ return { ...stage, groups };
+ });
+};
+
+const unwrapNodesWithName = (jobArray, prop, field = 'name') => {
+ return jobArray.map(job => {
+ return { ...job, [prop]: job[prop].nodes.map(item => item[field]) };
+ });
+};
+
+const unwrapJobWithNeeds = denodedJobArray => {
+ return unwrapNodesWithName(denodedJobArray, 'needs');
+};
+
+const unwrapStagesWithNeeds = denodedStages => {
+ const unwrappedNestedGroups = unwrapGroups(denodedStages);
+
+ const nodes = unwrappedNestedGroups.map(node => {
+ const { groups } = node;
+ const groupsWithJobs = groups.map(group => {
+ const jobs = unwrapJobWithNeeds(group.jobs.nodes);
+ return { ...group, jobs };
+ });
+
+ return { ...node, groups: groupsWithJobs };
+ });
+
+ return nodes;
+};
+
+export { unwrapGroups, unwrapNodesWithName, unwrapJobWithNeeds, unwrapStagesWithNeeds };
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 2d0bb0bfebc..3fe972d1917 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -72,6 +72,16 @@ class Admin::UsersController < Admin::ApplicationController
end
end
+ def reject
+ result = Users::RejectService.new(current_user).execute(user)
+
+ if result[:status] == :success
+ redirect_to admin_users_path, status: :found, notice: _("You've rejected %{user}" % { user: user.name })
+ else
+ redirect_back_or_admin_user(alert: result[:message])
+ end
+ end
+
def activate
return redirect_back_or_admin_user(notice: _("Error occurred. A blocked user must be unblocked to be activated")) if user.blocked?
diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb
index 8e9b038437d..86968b99ce6 100644
--- a/app/controllers/concerns/integrations_actions.rb
+++ b/app/controllers/concerns/integrations_actions.rb
@@ -43,6 +43,12 @@ module IntegrationsActions
render json: {}, status: :ok
end
+ def reset
+ flash[:notice] = s_('Integrations|This integration, and inheriting projects were reset.')
+
+ render json: {}, status: :ok
+ end
+
private
def integrations_enabled?
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 26fc1c11f6d..4224802b2b0 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -109,15 +109,6 @@ class InvitesController < ApplicationController
end
def track_invitation_reminders_experiment(action)
- return unless Gitlab::Experimentation.enabled?(:invitation_reminders)
-
- property = Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, member.invite_email) ? 'experimental_group' : 'control_group'
-
- Gitlab::Tracking.event(
- Gitlab::Experimentation.experiment(:invitation_reminders).tracking_category,
- action,
- property: property,
- label: Digest::MD5.hexdigest(member.to_global_id.to_s)
- )
+ track_experiment_event(:invitation_reminders, action, subject: member)
end
end
diff --git a/app/graphql/types/merge_request_connection_type.rb b/app/graphql/types/merge_request_connection_type.rb
new file mode 100644
index 00000000000..da06bb86929
--- /dev/null
+++ b/app/graphql/types/merge_request_connection_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class MergeRequestConnectionType < Types::CountableConnectionType
+ field :total_time_to_merge, GraphQL::FLOAT_TYPE, null: true,
+ description: 'Total sum of time to merge, in seconds, for the collection of merge requests'
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def total_time_to_merge
+ object.items.reorder(nil).total_time_to_merge
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 91cc26868fb..bc8871cfa39 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -4,7 +4,7 @@ module Types
class MergeRequestType < BaseObject
graphql_name 'MergeRequest'
- connection_type_class(Types::CountableConnectionType)
+ connection_type_class(Types::MergeRequestConnectionType)
implements(Types::Notes::NoteableType)
implements(Types::CurrentUserTodos)
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 96eb14be4b4..b7e11d7cbb6 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -75,7 +75,15 @@ module ServicesHelper
end
end
- def integration_form_data(integration)
+ def scoped_reset_integration_path(integration, group: nil)
+ if group.present?
+ reset_group_settings_integration_path(group, integration)
+ else
+ reset_admin_application_settings_integration_path(integration)
+ end
+ end
+
+ def integration_form_data(integration, group: nil)
{
id: integration.id,
show_active: integration.show_active_box?.to_s,
@@ -94,7 +102,7 @@ module ServicesHelper
cancel_path: scoped_integrations_path,
can_test: integration.can_test?.to_s,
test_path: scoped_test_integration_path(integration),
- reset_path: ''
+ reset_path: reset_integrations?(group: group) ? scoped_reset_integration_path(integration, group: group) : ''
}
end
@@ -122,6 +130,10 @@ module ServicesHelper
!Gitlab.com?
end
+ def reset_integrations?(group: nil)
+ Feature.enabled?(:reset_integrations, group, type: :development)
+ end
+
extend self
private
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 0b5a8dfdc24..350b7c2aeec 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -64,11 +64,11 @@ module Emails
layout: 'unknown_user_mailer'
)
- if Gitlab::Experimentation.enabled?(:invitation_reminders)
+ if Gitlab::Experimentation.active?(:invitation_reminders)
Gitlab::Tracking.event(
- Gitlab::Experimentation.experiment(:invitation_reminders).tracking_category,
+ Gitlab::Experimentation.get_experiment(:invitation_reminders).tracking_category,
'sent',
- property: Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, member.invite_email) ? 'experimental_group' : 'control_group',
+ property: Gitlab::Experimentation.in_experiment_group?(:invitation_reminders, subject: member.invite_email) ? 'experimental_group' : 'control_group',
label: Digest::MD5.hexdigest(member.to_global_id.to_s)
)
end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 6f44b63f8d0..e3c72a343e7 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -18,6 +18,14 @@ module Emails
subject: subject(_("GitLab Account Request")))
end
+ def user_admin_rejection_email(name, email)
+ @name = name
+
+ profile_email_with_layout(
+ to: email,
+ subject: subject(_("GitLab account request rejected")))
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def new_ssh_key_email(key_id)
@key = Key.find_by(id: key_id)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7687089942a..e3f44492e13 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -339,6 +339,13 @@ class MergeRequest < ApplicationRecord
)
end
+ def self.total_time_to_merge
+ join_metrics
+ .merge(MergeRequest::Metrics.with_valid_time_to_merge)
+ .pluck(MergeRequest::Metrics.time_to_merge_expression)
+ .first
+ end
+
after_save :keep_around_commit, unless: :importing?
alias_attribute :project, :target_project
diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb
index 66bff3f5982..d3fe256fb1b 100644
--- a/app/models/merge_request/metrics.rb
+++ b/app/models/merge_request/metrics.rb
@@ -10,6 +10,11 @@ class MergeRequest::Metrics < ApplicationRecord
scope :merged_after, ->(date) { where(arel_table[:merged_at].gteq(date)) }
scope :merged_before, ->(date) { where(arel_table[:merged_at].lteq(date)) }
+ scope :with_valid_time_to_merge, -> { where(arel_table[:merged_at].gt(arel_table[:created_at])) }
+
+ def self.time_to_merge_expression
+ Arel.sql('EXTRACT(epoch FROM SUM(AGE(merge_request_metrics.merged_at, merge_request_metrics.created_at)))')
+ end
private
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 686e936808a..b5c1ec0181e 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -99,6 +99,7 @@ class GlobalPolicy < BasePolicy
enable :read_custom_attribute
enable :update_custom_attribute
enable :approve_user
+ enable :reject_user
end
# We can't use `read_statistics` because the user may have different permissions for different projects
diff --git a/app/services/members/invitation_reminder_email_service.rb b/app/services/members/invitation_reminder_email_service.rb
index e589cdc2fa3..7ce6ddb97ef 100644
--- a/app/services/members/invitation_reminder_email_service.rb
+++ b/app/services/members/invitation_reminder_email_service.rb
@@ -25,7 +25,7 @@ module Members
private
def experiment_enabled?
- Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, invitation.invite_email)
+ Gitlab::Experimentation.in_experiment_group?(:invitation_reminders, subject: invitation.invite_email)
end
def days_after_invitation_sent
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index b45923a5742..993b1c7a928 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -380,6 +380,10 @@ class NotificationService
end
end
+ def user_admin_rejection(name, email)
+ mailer.user_admin_rejection_email(name, email).deliver_later
+ end
+
# Members
def new_access_request(member)
return true unless member.notifiable?(:subscription)
diff --git a/app/services/users/reject_service.rb b/app/services/users/reject_service.rb
new file mode 100644
index 00000000000..dd72547c688
--- /dev/null
+++ b/app/services/users/reject_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Users
+ class RejectService < BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user)
+ return error(_('You are not allowed to reject a user')) unless allowed?
+ return error(_('This user does not have a pending request')) unless user.blocked_pending_approval?
+
+ user.delete_async(deleted_by: current_user, params: { hard_delete: true })
+
+ NotificationService.new.user_admin_rejection(user.name, user.email)
+
+ success
+ end
+
+ private
+
+ attr_reader :current_user
+
+ def allowed?
+ can?(current_user, :reject_user)
+ end
+ end
+end
diff --git a/app/views/admin/users/_reject_pending_user.html.haml b/app/views/admin/users/_reject_pending_user.html.haml
new file mode 100644
index 00000000000..17108427330
--- /dev/null
+++ b/app/views/admin/users/_reject_pending_user.html.haml
@@ -0,0 +1,7 @@
+.card.border-danger
+ .card-header.bg-danger.gl-text-white
+ = s_('AdminUsers|This user has requested access')
+ .card-body
+ = render partial: 'admin/users/user_reject_effects'
+ %br
+ = link_to s_('AdminUsers|Reject request'), reject_admin_user_path(user), method: :delete, class: "btn gl-button btn-danger", data: { confirm: s_('AdminUsers|Are you sure?') }
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index 905f2946370..31fd3aea94d 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -37,8 +37,7 @@
- elsif user.blocked?
- if user.blocked_pending_approval?
= link_to s_('AdminUsers|Approve'), approve_admin_user_path(user), method: :put
- %button.btn.btn-default-tertiary.js-confirm-modal-button{ data: user_block_data(user, user_block_effects) }
- = s_('AdminUsers|Block')
+ = link_to s_('AdminUsers|Reject'), reject_admin_user_path(user), method: :delete
- else
%button.btn.btn-default-tertiary.js-confirm-modal-button{ data: user_unblock_data(user) }
= s_('AdminUsers|Unblock')
@@ -56,7 +55,7 @@
- if user.access_locked?
%li
= link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') }
- - if can?(current_user, :destroy_user, user)
+ - if can?(current_user, :destroy_user, user) && !user.blocked_pending_approval?
%li.divider
- if user.can_be_removed?
%li
diff --git a/app/views/admin/users/_user_reject_effects.html.haml b/app/views/admin/users/_user_reject_effects.html.haml
new file mode 100644
index 00000000000..17b6862b0cc
--- /dev/null
+++ b/app/views/admin/users/_user_reject_effects.html.haml
@@ -0,0 +1,10 @@
+%p
+ = s_('AdminUsers|Rejected users:')
+%ul
+ %li
+ = s_('AdminUsers|Cannot sign in or access instance information')
+ %li
+ = s_('AdminUsers|Will be deleted')
+%p
+ - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path("user/profile/account/delete_account", anchor: "associated-records") }
+ = s_('AdminUsers|For more information, please refer to the %{link_start}user account deletion documentation.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 85545e33f0c..26f78ea4d6a 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -172,7 +172,7 @@
- if @user.blocked?
- if @user.blocked_pending_approval?
= render 'admin/users/approve_user', user: @user
- = render 'admin/users/block_user', user: @user
+ = render 'admin/users/reject_pending_user', user: @user
- else
.card.border-info
.card-header.gl-bg-blue-500.gl-text-white
@@ -196,52 +196,52 @@
%p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account.
%br
= link_to 'Unlock user', unlock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: 'Are you sure?' }
-
- .card.border-danger
- .card-header.bg-danger.text-white
- = s_('AdminUsers|Delete user')
- .card-body
- - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
- %p Deleting a user has the following effects:
- = render 'users/deletion_guidance', user: @user
- %br
- %button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete',
- delete_user_url: admin_user_path(@user),
- block_user_url: block_admin_user_path(@user),
- username: sanitize_name(@user.name) } }
- = s_('AdminUsers|Delete user')
- - else
- - if @user.solo_owned_groups.present?
- %p
- This user is currently an owner in these groups:
- %strong= @user.solo_owned_groups.map(&:name).join(', ')
+ - if !@user.blocked_pending_approval?
+ .card.border-danger
+ .card-header.bg-danger.text-white
+ = s_('AdminUsers|Delete user')
+ .card-body
+ - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
+ %p Deleting a user has the following effects:
+ = render 'users/deletion_guidance', user: @user
+ %br
+ %button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete',
+ delete_user_url: admin_user_path(@user),
+ block_user_url: block_admin_user_path(@user),
+ username: sanitize_name(@user.name) } }
+ = s_('AdminUsers|Delete user')
+ - else
+ - if @user.solo_owned_groups.present?
+ %p
+ This user is currently an owner in these groups:
+ %strong= @user.solo_owned_groups.map(&:name).join(', ')
+ %p
+ You must transfer ownership or delete these groups before you can delete this user.
+ - else
+ %p
+ You don't have access to delete this user.
+
+ .card.border-danger
+ .card-header.bg-danger.text-white
+ = s_('AdminUsers|Delete user and contributions')
+ .card-body
+ - if can?(current_user, :destroy_user, @user)
%p
- You must transfer ownership or delete these groups before you can delete this user.
+ This option deletes the user and any contributions that
+ would usually be moved to the
+ = succeed "." do
+ = link_to "system ghost user", help_page_path("user/profile/account/delete_account")
+ As well as the user's personal projects, groups owned solely by
+ the user, and projects in them, will also be removed. Commits
+ to other projects are unaffected.
+ %br
+ %button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
+ delete_user_url: admin_user_path(@user, hard_delete: true),
+ block_user_url: block_admin_user_path(@user),
+ username: @user.name } }
+ = s_('AdminUsers|Delete user and contributions')
- else
%p
You don't have access to delete this user.
- .card.border-danger
- .card-header.bg-danger.text-white
- = s_('AdminUsers|Delete user and contributions')
- .card-body
- - if can?(current_user, :destroy_user, @user)
- %p
- This option deletes the user and any contributions that
- would usually be moved to the
- = succeed "." do
- = link_to "system ghost user", help_page_path("user/profile/account/delete_account")
- As well as the user's personal projects, groups owned solely by
- the user, and projects in them, will also be removed. Commits
- to other projects are unaffected.
- %br
- %button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
- delete_user_url: admin_user_path(@user, hard_delete: true),
- block_user_url: block_admin_user_path(@user),
- username: @user.name } }
- = s_('AdminUsers|Delete user and contributions')
- - else
- %p
- You don't have access to delete this user.
-
= render partial: 'admin/users/modals'
diff --git a/app/views/notify/user_admin_rejection_email.html.haml b/app/views/notify/user_admin_rejection_email.html.haml
new file mode 100644
index 00000000000..24d6c05fa38
--- /dev/null
+++ b/app/views/notify/user_admin_rejection_email.html.haml
@@ -0,0 +1,5 @@
+= email_default_heading(_('Hello %{name},') % { name: @name })
+%p
+ = _('Your request to join %{host} has been rejected.').html_safe % { host: link_to(root_url, root_url) }
+%p
+ = _('Please contact your GitLab administrator if you think this is an error.')
diff --git a/app/views/notify/user_admin_rejection_email.text.erb b/app/views/notify/user_admin_rejection_email.text.erb
new file mode 100644
index 00000000000..cc676b82934
--- /dev/null
+++ b/app/views/notify/user_admin_rejection_email.text.erb
@@ -0,0 +1,6 @@
+<%= _('Hello %{name},') % { name: @name } %>
+
+<%= _('Your request to join %{host} has been rejected.') % { host: root_url } %>
+
+<%= _('Please contact your GitLab administrator if you think this is an error.') %>
+
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 647421a8fbe..194e0eb57f2 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -9,5 +9,5 @@
.service-settings
- if @default_integration
- .js-vue-default-integration-settings{ data: integration_form_data(@default_integration) }
- .js-vue-integration-settings{ data: integration_form_data(integration) }
+ .js-vue-default-integration-settings{ data: integration_form_data(@default_integration, group: @group) }
+ .js-vue-integration-settings{ data: integration_form_data(integration, group: @group) }
diff --git a/app/workers/member_invitation_reminder_emails_worker.rb b/app/workers/member_invitation_reminder_emails_worker.rb
index 50f583005c0..97aa9c7e065 100644
--- a/app/workers/member_invitation_reminder_emails_worker.rb
+++ b/app/workers/member_invitation_reminder_emails_worker.rb
@@ -8,7 +8,7 @@ class MemberInvitationReminderEmailsWorker # rubocop:disable Scalability/Idempot
urgency :low
def perform
- return unless Gitlab::Experimentation.enabled?(:invitation_reminders)
+ return unless Gitlab::Experimentation.active?(:invitation_reminders)
Member.not_accepted_invitations.not_expired.last_ten_days_excluding_today.find_in_batches do |invitations|
invitations.each do |invitation|