diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-03 03:09:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-03 03:09:53 +0300 |
commit | 1502c20d04c7ff8d719175c76b0a2507ab390172 (patch) | |
tree | dc01bfe0877bd93e7047db28dd972c7d597a527b /app | |
parent | f96f2720d1b21b76eadedc54fdea67cb70e98d94 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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| |