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>2022-03-14 21:07:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-14 21:07:46 +0300
commitfbf2955cfc9ffc319d57960a0b0df1ee1b5fd05f (patch)
tree6964ec0aaac3d432a4795878e87d78566f7bf719 /app
parent739467f1fa4d5d4042b47ff6637a567d1ad6a4a4 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_actions.vue21
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters_main_view.vue24
-rw-r--r--app/assets/javascripts/clusters_list/constants.js25
-rw-r--r--app/assets/javascripts/clusters_list/index.js2
-rw-r--r--app/assets/javascripts/content_editor/extensions/paste_markdown.js6
-rw-r--r--app/assets/javascripts/content_editor/services/content_editor.js7
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_deserializer.js12
-rw-r--r--app/assets/javascripts/jobs/components/log/line_header.vue2
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/clusters_controller.rb1
-rw-r--r--app/controllers/groups/clusters_controller.rb1
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb2
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb2
-rw-r--r--app/graphql/mutations/ci/runners_registration_token/reset.rb13
-rw-r--r--app/helpers/application_settings_helper.rb5
-rw-r--r--app/helpers/clusters_helper.rb3
-rw-r--r--app/models/concerns/bulk_member_access_load.rb52
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/project.rb9
-rw-r--r--app/models/project_authorization.rb2
-rw-r--r--app/models/project_team.rb4
-rw-r--r--app/models/projects/triggered_hooks.rb25
-rw-r--r--app/models/user.rb8
-rw-r--r--app/policies/application_setting_policy.rb5
-rw-r--r--app/policies/global_policy.rb1
-rw-r--r--app/services/ci/runners/reset_registration_token_service.rb31
-rw-r--r--app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml19
-rw-r--r--app/views/projects/_new_project_fields.html.haml3
28 files changed, 178 insertions, 113 deletions
diff --git a/app/assets/javascripts/clusters_list/components/clusters_actions.vue b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
index 81aa867ab00..ccb973f1eb8 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_actions.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_actions.vue
@@ -1,5 +1,6 @@
<script>
import {
+ GlButton,
GlDropdown,
GlDropdownItem,
GlModalDirective,
@@ -14,6 +15,7 @@ export default {
i18n: CLUSTERS_ACTIONS,
INSTALL_AGENT_MODAL_ID,
components: {
+ GlButton,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
@@ -23,7 +25,13 @@ export default {
GlModalDirective,
GlTooltip: GlTooltipDirective,
},
- inject: ['newClusterPath', 'addClusterPath', 'canAddCluster', 'displayClusterAgents'],
+ inject: [
+ 'newClusterPath',
+ 'addClusterPath',
+ 'canAddCluster',
+ 'displayClusterAgents',
+ 'certificateBasedClustersEnabled',
+ ],
computed: {
tooltip() {
const { connectWithAgent, connectExistingCluster, dropdownDisabledHint } = this.$options.i18n;
@@ -46,6 +54,7 @@ export default {
<template>
<div class="nav-controls gl-ml-auto">
<gl-dropdown
+ v-if="certificateBasedClustersEnabled"
ref="dropdown"
v-gl-modal-directive="shouldTriggerModal && $options.INSTALL_AGENT_MODAL_ID"
v-gl-tooltip="tooltip"
@@ -75,5 +84,15 @@ export default {
{{ $options.i18n.connectExistingCluster }}
</gl-dropdown-item>
</gl-dropdown>
+ <gl-button
+ v-else
+ v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
+ v-gl-tooltip="tooltip"
+ :disabled="!canAddCluster"
+ category="primary"
+ variant="confirm"
+ >
+ {{ $options.i18n.connectWithAgent }}
+ </gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/clusters_list/components/clusters_main_view.vue b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue
index eaaff74286a..aab6d3dc1f0 100644
--- a/app/assets/javascripts/clusters_list/components/clusters_main_view.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue
@@ -9,6 +9,7 @@ import {
AGENT,
EVENT_LABEL_TABS,
EVENT_ACTIONS_CHANGE,
+ AGENT_TAB,
} from '../constants';
import Agents from './agents.vue';
import InstallAgentModal from './install_agent_modal.vue';
@@ -28,9 +29,8 @@ export default {
Agents,
InstallAgentModal,
},
- CLUSTERS_TABS,
mixins: [trackingMixin],
- inject: ['displayClusterAgents'],
+ inject: ['displayClusterAgents', 'certificateBasedClustersEnabled'],
props: {
defaultBranchName: {
default: '.noBranch',
@@ -45,21 +45,27 @@ export default {
};
},
computed: {
- clusterTabs() {
- return this.displayClusterAgents ? CLUSTERS_TABS : [CERTIFICATE_TAB];
+ availableTabs() {
+ const clusterTabs = this.displayClusterAgents ? CLUSTERS_TABS : [CERTIFICATE_TAB];
+ return this.certificateBasedClustersEnabled ? clusterTabs : [AGENT_TAB];
},
},
watch: {
- selectedTabIndex(val) {
- this.onTabChange(val);
+ selectedTabIndex: {
+ handler(val) {
+ this.onTabChange(val);
+ },
+ immediate: true,
},
},
methods: {
setSelectedTab(tabName) {
- this.selectedTabIndex = this.clusterTabs.findIndex((tab) => tab.queryParamValue === tabName);
+ this.selectedTabIndex = this.availableTabs.findIndex(
+ (tab) => tab.queryParamValue === tabName,
+ );
},
onTabChange(tab) {
- const tabName = this.clusterTabs[tab].queryParamValue;
+ const tabName = this.availableTabs[tab].queryParamValue;
this.maxAgents = tabName === AGENT ? MAX_LIST_COUNT : MAX_CLUSTERS_LIST;
this.track(EVENT_ACTIONS_CHANGE, { property: tabName });
@@ -76,7 +82,7 @@ export default {
lazy
>
<gl-tab
- v-for="(tab, idx) in clusterTabs"
+ v-for="(tab, idx) in availableTabs"
:key="idx"
:title="tab.title"
:query-param-value="tab.queryParamValue"
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index ba7eedcf6bf..046a3f7e4fc 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -232,25 +232,24 @@ export const CERTIFICATE_BASED_CARD_INFO = {
export const MAX_CLUSTERS_LIST = 6;
+export const ALL_TAB = {
+ title: s__('ClusterAgents|All'),
+ component: 'ClustersViewAll',
+ queryParamValue: 'all',
+};
+
+export const AGENT_TAB = {
+ title: s__('ClusterAgents|Agent'),
+ component: 'agents',
+ queryParamValue: 'agent',
+};
export const CERTIFICATE_TAB = {
title: s__('ClusterAgents|Certificate'),
component: 'clusters',
queryParamValue: 'certificate_based',
};
-export const CLUSTERS_TABS = [
- {
- title: s__('ClusterAgents|All'),
- component: 'ClustersViewAll',
- queryParamValue: 'all',
- },
- {
- title: s__('ClusterAgents|Agent'),
- component: 'agents',
- queryParamValue: 'agent',
- },
- CERTIFICATE_TAB,
-];
+export const CLUSTERS_TABS = [ALL_TAB, AGENT_TAB, CERTIFICATE_TAB];
export const CLUSTERS_ACTIONS = {
actionsButton: s__('ClusterAgents|Actions'),
diff --git a/app/assets/javascripts/clusters_list/index.js b/app/assets/javascripts/clusters_list/index.js
index da1774b3135..27eebc9d891 100644
--- a/app/assets/javascripts/clusters_list/index.js
+++ b/app/assets/javascripts/clusters_list/index.js
@@ -31,6 +31,7 @@ export default () => {
canAdminCluster,
gitlabVersion,
displayClusterAgents,
+ certificateBasedClustersEnabled,
} = el.dataset;
return new Vue({
@@ -48,6 +49,7 @@ export default () => {
canAdminCluster: parseBoolean(canAdminCluster),
gitlabVersion,
displayClusterAgents: parseBoolean(displayClusterAgents),
+ certificateBasedClustersEnabled: parseBoolean(certificateBasedClustersEnabled),
},
store: createStore(el.dataset),
render(createElement) {
diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/paste_markdown.js
index 31774e0ec51..c349aa42a62 100644
--- a/app/assets/javascripts/content_editor/extensions/paste_markdown.js
+++ b/app/assets/javascripts/content_editor/extensions/paste_markdown.js
@@ -34,15 +34,15 @@ export default Extension.create({
deserializer
.deserialize({ schema: editor.schema, content: markdown })
- .then((doc) => {
- if (!doc) {
+ .then(({ document }) => {
+ if (!document) {
return;
}
const { state, view } = editor;
const { tr, selection } = state;
- tr.replaceWith(selection.from - 1, selection.to, doc.content);
+ tr.replaceWith(selection.from - 1, selection.to, document.content);
view.dispatch(tr);
eventHub.$emit(LOADING_SUCCESS_EVENT);
})
diff --git a/app/assets/javascripts/content_editor/services/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js
index e0995a5974c..c5638da2daf 100644
--- a/app/assets/javascripts/content_editor/services/content_editor.js
+++ b/app/assets/javascripts/content_editor/services/content_editor.js
@@ -40,13 +40,14 @@ export class ContentEditor {
try {
eventHub.$emit(LOADING_CONTENT_EVENT);
- const newDoc = await deserializer.deserialize({
+ const { document } = await deserializer.deserialize({
schema: editor.schema,
content: serializedContent,
});
- if (newDoc) {
+
+ if (document) {
tr.setSelection(selection)
- .replaceSelectionWith(newDoc, false)
+ .replaceSelectionWith(document, false)
.setMeta('preventUpdate', true);
editor.view.dispatch(tr);
}
diff --git a/app/assets/javascripts/content_editor/services/markdown_deserializer.js b/app/assets/javascripts/content_editor/services/markdown_deserializer.js
index ccffcd4cee8..cd4863d8eac 100644
--- a/app/assets/javascripts/content_editor/services/markdown_deserializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_deserializer.js
@@ -4,16 +4,22 @@ export default ({ render }) => {
/**
* Converts a Markdown string into a ProseMirror JSONDocument based
* on a ProseMirror schema.
+ *
+ * @param {Object} options — The schema and content for deserialization
* @param {ProseMirror.Schema} params.schema A ProseMirror schema that defines
* the types of content supported in the document
* @param {String} params.content An arbitrary markdown string
- * @returns A ProseMirror JSONDocument
+ *
+ * @returns An object with the following properties:
+ * - document: A ProseMirror document object generated from the deserialized Markdown
+ * - dom: The Markdown Deserializer renders Markdown as HTML to generate the ProseMirror
+ * document. The dom property contains the HTML generated from the Markdown Source.
*/
return {
deserialize: async ({ schema, content }) => {
const html = await render(content);
- if (!html) return null;
+ if (!html) return {};
const parser = new DOMParser();
const { body } = parser.parseFromString(html, 'text/html');
@@ -21,7 +27,7 @@ export default ({ render }) => {
// append original source as a comment that nodes can access
body.append(document.createComment(content));
- return ProseMirrorDOMParser.fromSchema(schema).parse(body);
+ return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body), dom: body };
},
};
};
diff --git a/app/assets/javascripts/jobs/components/log/line_header.vue b/app/assets/javascripts/jobs/components/log/line_header.vue
index 3bb1f58573c..c72d488f844 100644
--- a/app/assets/javascripts/jobs/components/log/line_header.vue
+++ b/app/assets/javascripts/jobs/components/log/line_header.vue
@@ -43,7 +43,7 @@ export default {
<template>
<div
- class="log-line collapsible-line d-flex justify-content-between ws-normal"
+ class="log-line collapsible-line d-flex justify-content-between ws-normal gl-align-items-flex-start"
role="button"
@click="handleOnClick"
>
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 1d0930ba73c..f9df0307a01 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -71,7 +71,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def reset_registration_token
- @application_setting.reset_runners_registration_token!
+ ::Ci::Runners::ResetRegistrationTokenService.new(@application_setting, current_user).execute
flash[:notice] = _('New runners registration token has been generated!')
redirect_to admin_runners_path
diff --git a/app/controllers/admin/clusters_controller.rb b/app/controllers/admin/clusters_controller.rb
index 9a642e53d86..052c8821588 100644
--- a/app/controllers/admin/clusters_controller.rb
+++ b/app/controllers/admin/clusters_controller.rb
@@ -2,6 +2,7 @@
class Admin::ClustersController < Clusters::ClustersController
include EnforcesAdminAuthentication
+ before_action :ensure_feature_enabled!
layout 'admin'
diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb
index 666a96d6fc0..2fe9faa252f 100644
--- a/app/controllers/groups/clusters_controller.rb
+++ b/app/controllers/groups/clusters_controller.rb
@@ -3,6 +3,7 @@
class Groups::ClustersController < Clusters::ClustersController
include ControllerWithCrossProjectAccessCheck
+ before_action :ensure_feature_enabled!
prepend_before_action :group
requires_cross_project_access
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index a290ef9b5e7..9b9e3f7b0bc 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -36,7 +36,7 @@ module Groups
end
def reset_registration_token
- @group.reset_runners_token!
+ ::Ci::Runners::ResetRegistrationTokenService.new(@group, current_user).execute
flash[:notice] = _('GroupSettings|New runners registration token has been generated!')
redirect_to group_settings_ci_cd_path
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index dd2fb57f7ac..3f4d26bb6ec 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -64,7 +64,7 @@ module Projects
end
def reset_registration_token
- @project.reset_runners_token!
+ ::Ci::Runners::ResetRegistrationTokenService.new(@project, current_user).execute
flash[:toast] = _("New runners registration token has been generated!")
redirect_to namespace_project_settings_ci_cd_path
diff --git a/app/graphql/mutations/ci/runners_registration_token/reset.rb b/app/graphql/mutations/ci/runners_registration_token/reset.rb
index 7976e8fb70d..29ef7aa2e81 100644
--- a/app/graphql/mutations/ci/runners_registration_token/reset.rb
+++ b/app/graphql/mutations/ci/runners_registration_token/reset.rb
@@ -45,20 +45,19 @@ module Mutations
def reset_token(type:, **args)
id = args[:id]
+ scope = nil
case type
when 'instance_type'
raise Gitlab::Graphql::Errors::ArgumentError, "id must not be specified for '#{type}' scope" if id.present?
- authorize!(:global)
-
- ApplicationSetting.current.reset_runners_registration_token!
- ApplicationSetting.current_without_cache.runners_registration_token
+ scope = ApplicationSetting.current
+ authorize!(scope)
when 'group_type', 'project_type'
- project_or_group = authorized_find!(type: type, id: id)
- project_or_group.reset_runners_token!
- project_or_group.runners_token
+ scope = authorized_find!(type: type, id: id)
end
+
+ ::Ci::Runners::ResetRegistrationTokenService.new(scope, current_user).execute if scope
end
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index d9a1731e820..a9c13b2fdeb 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -465,7 +465,10 @@ module ApplicationSettingsHelper
end
def instance_clusters_enabled?
- can?(current_user, :read_cluster, Clusters::Instance.new)
+ clusterable = Clusters::Instance.new
+
+ Feature.enabled?(:certificate_based_clusters, clusterable, default_enabled: :yaml, type: :ops) &&
+ can?(current_user, :read_cluster, clusterable)
end
def omnibus_protected_paths_throttle?
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index ae890685dc6..62d93d75b11 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -31,7 +31,8 @@ module ClustersHelper
add_cluster_path: clusterable.new_path(tab: 'add'),
can_add_cluster: clusterable.can_add_cluster?.to_s,
can_admin_cluster: clusterable.can_admin_cluster?.to_s,
- display_cluster_agents: display_cluster_agents?(clusterable).to_s
+ display_cluster_agents: display_cluster_agents?(clusterable).to_s,
+ certificate_based_clusters_enabled: Feature.enabled?(:certificate_based_clusters, clusterable, default_enabled: :yaml, type: :ops).to_s
}
end
diff --git a/app/models/concerns/bulk_member_access_load.rb b/app/models/concerns/bulk_member_access_load.rb
index 927d6ccb28f..efc65e55e40 100644
--- a/app/models/concerns/bulk_member_access_load.rb
+++ b/app/models/concerns/bulk_member_access_load.rb
@@ -1,61 +1,19 @@
# frozen_string_literal: true
-# Returns and caches in thread max member access for a resource
-#
module BulkMemberAccessLoad
extend ActiveSupport::Concern
included do
- # Determine the maximum access level for a group of resources in bulk.
- #
- # Returns a Hash mapping resource ID -> maximum access level.
- def max_member_access_for_resource_ids(resource_klass, resource_ids, &block)
- raise 'Block is mandatory' unless block_given?
-
- memoization_index = self.id
- memoization_class = self.class
-
- resource_ids = resource_ids.uniq
- memo_id = "#{memoization_class}:#{memoization_index}"
- access = load_access_hash(resource_klass, memo_id)
-
- # Look up only the IDs we need
- resource_ids -= access.keys
-
- return access if resource_ids.empty?
-
- resource_access = yield(resource_ids)
-
- access.merge!(resource_access)
-
- missing_resource_ids = resource_ids - resource_access.keys
-
- missing_resource_ids.each do |resource_id|
- access[resource_id] = Gitlab::Access::NO_ACCESS
- end
-
- access
- end
-
def merge_value_to_request_store(resource_klass, resource_id, value)
- max_member_access_for_resource_ids(resource_klass, [resource_id]) do
+ Gitlab::SafeRequestLoader.execute(resource_key: max_member_access_for_resource_key(resource_klass),
+ resource_ids: [resource_id],
+ default_value: Gitlab::Access::NO_ACCESS) do
{ resource_id => value }
end
end
- private
-
- def max_member_access_for_resource_key(klass, memoization_index)
- "max_member_access_for_#{klass.name.underscore.pluralize}:#{memoization_index}"
- end
-
- def load_access_hash(resource_klass, memo_id)
- return {} unless Gitlab::SafeRequestStore.active?
-
- key = max_member_access_for_resource_key(resource_klass, memo_id)
- Gitlab::SafeRequestStore[key] ||= {}
-
- Gitlab::SafeRequestStore[key]
+ def max_member_access_for_resource_key(klass)
+ "max_member_access_for_#{klass.name.underscore.pluralize}:#{self.class}:#{self.id}"
end
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index e9fb4c36ba6..14d088dd38b 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -816,7 +816,9 @@ class Group < Namespace
private
def max_member_access(user_ids)
- max_member_access_for_resource_ids(User, user_ids) do |user_ids|
+ Gitlab::SafeRequestLoader.execute(resource_key: max_member_access_for_resource_key(User),
+ resource_ids: user_ids,
+ default_value: Gitlab::Access::NO_ACCESS) do |user_ids|
members_with_parents.where(user_id: user_ids).group(:user_id).maximum(:access_level)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 6457cce9364..e55395b32e7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1567,14 +1567,17 @@ class Project < ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do
- hooks.hooks_for(hooks_scope).select_active(hooks_scope, data).each do |hook|
- hook.async_execute(data, hooks_scope.to_s)
- end
+ triggered_hooks(hooks_scope, data).execute
SystemHooksService.new.execute_hooks(data, hooks_scope)
end
end
# rubocop: enable CodeReuse/ServiceClass
+ def triggered_hooks(hooks_scope, data)
+ triggered = ::Projects::TriggeredHooks.new(hooks_scope, data)
+ triggered.add_hooks(hooks)
+ end
+
def execute_integrations(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope
run_after_commit_or_now do
diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb
index c76332b21cd..5c6fdec16ca 100644
--- a/app/models/project_authorization.rb
+++ b/app/models/project_authorization.rb
@@ -9,7 +9,7 @@ class ProjectAuthorization < ApplicationRecord
validates :project, presence: true
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
- validates :user, uniqueness: { scope: [:project, :access_level] }, presence: true
+ validates :user, uniqueness: { scope: :project }, presence: true
def self.select_from_union(relations)
from_union(relations)
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index ee5ecc2dd3c..d5e0d112aeb 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -179,7 +179,9 @@ class ProjectTeam
#
# Returns a Hash mapping user ID -> maximum access level.
def max_member_access_for_user_ids(user_ids)
- project.max_member_access_for_resource_ids(User, user_ids) do |user_ids|
+ Gitlab::SafeRequestLoader.execute(resource_key: project.max_member_access_for_resource_key(User),
+ resource_ids: user_ids,
+ default_value: Gitlab::Access::NO_ACCESS) do |user_ids|
project.project_authorizations
.where(user: user_ids)
.group(:user_id)
diff --git a/app/models/projects/triggered_hooks.rb b/app/models/projects/triggered_hooks.rb
new file mode 100644
index 00000000000..e3aa3d106b7
--- /dev/null
+++ b/app/models/projects/triggered_hooks.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Projects
+ class TriggeredHooks
+ def initialize(scope, data)
+ @scope = scope
+ @data = data
+ @relations = []
+ end
+
+ def add_hooks(relation)
+ @relations << relation
+ self
+ end
+
+ def execute
+ # Assumes that the relations implement TriggerableHooks
+ @relations.each do |hooks|
+ hooks.hooks_for(@scope).select_active(@scope, @data).each do |hook|
+ hook.async_execute(@data, @scope.to_s)
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index fa58455ad35..4c375fe5230 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1862,7 +1862,9 @@ class User < ApplicationRecord
#
# Returns a Hash mapping project ID -> maximum access level.
def max_member_access_for_project_ids(project_ids)
- max_member_access_for_resource_ids(Project, project_ids) do |project_ids|
+ Gitlab::SafeRequestLoader.execute(resource_key: max_member_access_for_resource_key(Project),
+ resource_ids: project_ids,
+ default_value: Gitlab::Access::NO_ACCESS) do |project_ids|
project_authorizations.where(project: project_ids)
.group(:project_id)
.maximum(:access_level)
@@ -1877,7 +1879,9 @@ class User < ApplicationRecord
#
# Returns a Hash mapping project ID -> maximum access level.
def max_member_access_for_group_ids(group_ids)
- max_member_access_for_resource_ids(Group, group_ids) do |group_ids|
+ Gitlab::SafeRequestLoader.execute(resource_key: max_member_access_for_resource_key(Group),
+ resource_ids: group_ids,
+ default_value: Gitlab::Access::NO_ACCESS) do |group_ids|
group_members.where(source: group_ids).group(:source_id).maximum(:access_level)
end
end
diff --git a/app/policies/application_setting_policy.rb b/app/policies/application_setting_policy.rb
index 114c71fd99d..6d0b5f36fa4 100644
--- a/app/policies/application_setting_policy.rb
+++ b/app/policies/application_setting_policy.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
class ApplicationSettingPolicy < BasePolicy # rubocop:disable Gitlab/NamespacedClass
- rule { admin }.enable :read_application_setting
+ rule { admin }.policy do
+ enable :read_application_setting
+ enable :update_runners_registration_token
+ end
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 2a2ddf29899..fa7b117f3cd 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -115,7 +115,6 @@ class GlobalPolicy < BasePolicy
enable :approve_user
enable :reject_user
enable :read_usage_trends_measurement
- enable :update_runners_registration_token
end
# We can't use `read_statistics` because the user may have different permissions for different projects
diff --git a/app/services/ci/runners/reset_registration_token_service.rb b/app/services/ci/runners/reset_registration_token_service.rb
new file mode 100644
index 00000000000..bbe49c04644
--- /dev/null
+++ b/app/services/ci/runners/reset_registration_token_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Ci
+ module Runners
+ class ResetRegistrationTokenService
+ # @param [ApplicationSetting, Project, Group] scope: the scope of the reset operation
+ # @param [User] user: the user performing the operation
+ def initialize(scope, user)
+ @scope = scope
+ @user = user
+ end
+
+ def execute
+ return unless @user.present? && @user.can?(:update_runners_registration_token, scope)
+
+ case scope
+ when ::ApplicationSetting
+ scope.reset_runners_registration_token!
+ ApplicationSetting.current_without_cache.runners_registration_token
+ when ::Group, ::Project
+ scope.reset_runners_token!
+ scope.runners_token
+ end
+ end
+
+ private
+
+ attr_reader :scope, :user
+ end
+ end
+end
diff --git a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index 9d249931a34..3a4632affdc 100644
--- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
@@ -1,11 +1,10 @@
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
-.gcp-signup-offer.gl-alert.gl-alert-info.gl-my-3{ role: 'alert', data: { feature_id: Users::CalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: callouts_path } }
- .gl-alert-container
- %button.js-close.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon{ type: 'button', 'aria-label' => _('Dismiss') }
- = sprite_icon('close', size: 16, css_class: 'gl-icon')
- = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
- .gl-alert-content
- %h4.gl-alert-title= s_('ClusterIntegration|Did you know?')
- %p.gl-alert-body= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
- %a.gl-button.btn-confirm.text-decoration-none{ href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', rel: 'noopener noreferrer' }
- = s_("ClusterIntegration|Apply for credit")
+= render 'shared/global_alert',
+ title: s_('ClusterIntegration|Did you know?'),
+ alert_class: 'gcp-signup-offer',
+ alert_data: { feature_id: Users::CalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: callouts_path } do
+ .gl-alert-body
+ = s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
+ .gl-alert-actions
+ %a.gl-button.btn-confirm.text-decoration-none{ href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', rel: 'noopener noreferrer' }
+ = s_("ClusterIntegration|Apply for credit")
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index b8a15ab698c..2d0a4ae8605 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -46,7 +46,8 @@
= s_('ProjectsNew|Project description %{tag_start}(optional)%{tag_end}').html_safe % { tag_start: '<span>'.html_safe, tag_end: '</span>'.html_safe }
= f.text_area :description, placeholder: s_('ProjectsNew|Description format'), class: "form-control gl-form-input", rows: 3, maxlength: 250, data: { qa_selector: 'project_description', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_description", track_value: "" }
-.js-deployment-target-select
+- unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers?
+ .js-deployment-target-select
= f.label :visibility_level, class: 'label-bold' do
= s_('ProjectsNew|Visibility Level')