- {{
trimmedUsername
diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue
index 691a292673c..c1fb80959cf 100644
--- a/app/assets/javascripts/admin/users/components/user_actions.vue
+++ b/app/assets/javascripts/admin/users/components/user_actions.vue
@@ -139,6 +139,7 @@ export default {
:key="action"
:paths="userPaths"
:username="user.name"
+ :user-id="user.id"
:user-deletion-obstacles="obstaclesForUserDeletion"
:data-testid="`delete-${action}`"
>
diff --git a/app/assets/javascripts/api/user_api.js b/app/assets/javascripts/api/user_api.js
index 369abe95d49..0f874e35684 100644
--- a/app/assets/javascripts/api/user_api.js
+++ b/app/assets/javascripts/api/user_api.js
@@ -12,6 +12,7 @@ const USER_PROJECTS_PATH = '/api/:version/users/:id/projects';
const USER_POST_STATUS_PATH = '/api/:version/user/status';
const USER_FOLLOW_PATH = '/api/:version/users/:id/follow';
const USER_UNFOLLOW_PATH = '/api/:version/users/:id/unfollow';
+const USER_ASSOCIATIONS_COUNT_PATH = '/api/:version/users/:id/associations_count';
export function getUsers(query, options) {
const url = buildApiUrl(USERS_PATH);
@@ -81,3 +82,8 @@ export function unfollowUser(userId) {
const url = buildApiUrl(USER_UNFOLLOW_PATH).replace(':id', encodeURIComponent(userId));
return axios.post(url);
}
+
+export function associationsCount(userId) {
+ const url = buildApiUrl(USER_ASSOCIATIONS_COUNT_PATH).replace(':id', encodeURIComponent(userId));
+ return axios.get(url);
+}
diff --git a/app/assets/javascripts/blob/openapi/index.js b/app/assets/javascripts/blob/openapi/index.js
index 943001b7ec4..24a54358de5 100644
--- a/app/assets/javascripts/blob/openapi/index.js
+++ b/app/assets/javascripts/blob/openapi/index.js
@@ -5,7 +5,7 @@ const createSandbox = () => {
const iframeEl = document.createElement('iframe');
setAttributes(iframeEl, {
src: '/-/sandbox/swagger',
- sandbox: 'allow-scripts',
+ sandbox: 'allow-scripts allow-popups',
frameBorder: 0,
width: '100%',
// The height will be adjusted dynamically.
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 17ee2a0d8b6..96fc1649d49 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -289,7 +289,9 @@ export default class MergeRequestTabs {
}
if (action === 'commits') {
- this.loadCommits(href);
+ if (!this.commitsLoaded) {
+ this.loadCommits(href);
+ }
// this.hideSidebar();
this.resetViewContainer();
this.commitPipelinesTable = destroyPipelines(this.commitPipelinesTable);
@@ -423,28 +425,39 @@ export default class MergeRequestTabs {
return this.currentAction;
}
- loadCommits(source) {
- if (this.commitsLoaded) {
- return;
- }
-
+ loadCommits(source, page = 1) {
toggleLoader(true);
axios
- .get(`${source}.json`)
+ .get(`${source}.json`, { params: { page, per_page: 100 } })
.then(({ data }) => {
+ toggleLoader(false);
+
const commitsDiv = document.querySelector('div#commits');
// eslint-disable-next-line no-unsanitized/property
- commitsDiv.innerHTML = data.html;
+ commitsDiv.innerHTML += data.html;
localTimeAgo(commitsDiv.querySelectorAll('.js-timeago'));
this.commitsLoaded = true;
scrollToContainer('#commits');
- toggleLoader(false);
+ const loadMoreButton = document.querySelector('.js-load-more-commits');
- return import('./add_context_commits_modal');
+ if (loadMoreButton) {
+ loadMoreButton.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ loadMoreButton.remove();
+ this.loadCommits(source, loadMoreButton.dataset.nextPage);
+ });
+ }
+
+ if (!data.next_page) {
+ return import('./add_context_commits_modal');
+ }
+
+ return null;
})
- .then((m) => m.default())
+ .then((m) => m?.default())
.catch(() => {
toggleLoader(false);
createAlert({
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index f37a2987685..097b2f33aa9 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -3,7 +3,6 @@ import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
import LineHighlighter from '~/blob/line_highlighter';
-import initBlobBundle from '~/blob_edit/blob_bundle';
export default () => {
new LineHighlighter(); // eslint-disable-line no-new
@@ -35,6 +34,4 @@ export default () => {
suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
}).init();
-
- initBlobBundle();
};
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index cf7162f477d..17c17014ece 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,10 +1,8 @@
import $ from 'jquery';
import initTree from 'ee_else_ce/repository';
-import initBlob from '~/blob_edit/blob_bundle';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import NewCommitForm from '~/new_commit_form';
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
-initBlob();
initTree();
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 7a71c8800b4..d7315f2604d 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -1205,3 +1205,7 @@ $tabs-holder-z-index: 250;
margin-bottom: 0;
}
}
+
+.commits ol:not(:last-of-type) {
+ margin-bottom: 0;
+}
diff --git a/app/controllers/groups/observability_controller.rb b/app/controllers/groups/observability_controller.rb
index 5b6503494c4..1b7dde1293e 100644
--- a/app/controllers/groups/observability_controller.rb
+++ b/app/controllers/groups/observability_controller.rb
@@ -9,7 +9,7 @@ module Groups
default_frame_src = p.directives['frame-src'] || p.directives['default-src']
# When ObservabilityUI is not authenticated, it needs to be able to redirect to the GL sign-in page, hence 'self'
- frame_src_values = Array.wrap(default_frame_src) | [ObservabilityController.observability_url, "'self'"]
+ frame_src_values = Array.wrap(default_frame_src) | [observability_url, "'self'"]
p.frame_src(*frame_src_values)
end
@@ -18,10 +18,7 @@ module Groups
def index
# Format: https://observe.gitlab.com/-/GROUP_ID
- @observability_iframe_src = "#{ObservabilityController.observability_url}/-/#{@group.id}"
-
- # Uncomment below for testing with local GDK
- # @observability_iframe_src = "#{ObservabilityController.observability_url}/9970?groupId=14485840"
+ @observability_iframe_src = "#{observability_url}/-/#{@group.id}"
render layout: 'group', locals: { base_layout: 'layouts/fullscreen' }
end
@@ -29,15 +26,15 @@ module Groups
private
def self.observability_url
- return ENV['OVERRIDE_OBSERVABILITY_URL'] if ENV['OVERRIDE_OBSERVABILITY_URL']
- # TODO Make observability URL configurable https://gitlab.com/gitlab-org/opstrace/opstrace-ui/-/issues/80
- return "https://staging.observe.gitlab.com" if Gitlab.staging?
+ Gitlab::Observability.observability_url
+ end
- "https://observe.gitlab.com"
+ def observability_url
+ self.class.observability_url
end
def check_observability_allowed
- return render_404 unless self.class.observability_url.present?
+ return render_404 unless observability_url.present?
render_404 unless can?(current_user, :read_observability, @group)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 12356607494..daa193312bb 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -178,15 +178,15 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.recent_context_commits
)
- # Get commits from repository
- # or from cache if already merged
- @commits =
- set_commits_for_rendering(
- @merge_request.recent_commits(load_from_gitaly: true).with_latest_pipeline(@merge_request.source_branch).with_markdown_cache,
- commits_count: @merge_request.commits_count
- )
+ per_page = [(params[:per_page] || MergeRequestDiff::COMMITS_SAFE_SIZE).to_i, MergeRequestDiff::COMMITS_SAFE_SIZE].min
+ recent_commits = @merge_request.recent_commits(load_from_gitaly: true, limit: per_page, page: params[:page]).with_latest_pipeline(@merge_request.source_branch).with_markdown_cache
+ @next_page = recent_commits.next_page
+ @commits = set_commits_for_rendering(
+ recent_commits,
+ commits_count: @merge_request.commits_count
+ )
- render json: { html: view_to_html_string('projects/merge_requests/_commits') }
+ render json: { html: view_to_html_string('projects/merge_requests/_commits'), next_page: @next_page }
end
def pipelines
diff --git a/app/graphql/mutations/ci/runner/bulk_delete.rb b/app/graphql/mutations/ci/runner/bulk_delete.rb
index 4265099d28e..f053eda0f55 100644
--- a/app/graphql/mutations/ci/runner/bulk_delete.rb
+++ b/app/graphql/mutations/ci/runner/bulk_delete.rb
@@ -25,13 +25,12 @@ module Mutations
'Only present if operation was performed synchronously.'
def resolve(**runner_attrs)
- raise_resource_not_available_error! unless Ability.allowed?(current_user, :delete_runners)
-
if ids = runner_attrs[:ids]
- runners = find_all_runners_by_ids(model_ids_of(ids))
+ runner_ids = model_ids_of(ids)
+ runners = find_all_runners_by_ids(runner_ids)
- result = ::Ci::Runners::BulkDeleteRunnersService.new(runners: runners).execute
- result.payload.slice(:deleted_count, :deleted_ids).merge(errors: [])
+ result = ::Ci::Runners::BulkDeleteRunnersService.new(runners: runners, current_user: current_user).execute
+ result.payload.slice(:deleted_count, :deleted_ids, :errors)
else
{ errors: [] }
end
@@ -39,14 +38,15 @@ module Mutations
private
- def model_ids_of(ids)
- ids.filter_map { |gid| gid.model_id.to_i }
+ def model_ids_of(global_ids)
+ global_ids.filter_map { |gid| gid.model_id.to_i }
end
def find_all_runners_by_ids(ids)
return ::Ci::Runner.none if ids.blank?
- ::Ci::Runner.id_in(ids)
+ limit = ::Ci::Runners::BulkDeleteRunnersService::RUNNER_LIMIT
+ ::Ci::Runner.id_in(ids).limit(limit + 1)
end
end
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index c98cfed7493..49bf7aa638c 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -20,7 +20,7 @@ module Types
description: 'Timestamp of when the merge request was created.'
field :description, GraphQL::Types::String, null: true,
description: 'Description of the merge request (Markdown rendered as HTML for caching).'
- field :diff_head_sha, GraphQL::Types::String, null: true,
+ field :diff_head_sha, GraphQL::Types::String, null: true, calls_gitaly: true,
description: 'Diff head SHA of the merge request.'
field :diff_refs, Types::DiffRefsType, null: true,
description: 'References of the base SHA, the head SHA, and the start SHA for this merge request.'
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index e1aa0040ade..d75f81e2839 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -532,7 +532,7 @@ class ApplicationSetting < ApplicationRecord
validates :jira_connect_proxy_url,
length: { maximum: 255, message: N_('is too long (maximum is %{count} characters)') },
allow_blank: true,
- addressable_url: true
+ public_url: true
with_options(presence: true, numericality: { only_integer: true, greater_than: 0 }) do
validates :throttle_unauthenticated_api_requests_per_period
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index e1fbf7e3944..268ce403bf4 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -284,7 +284,11 @@ module Ci
return [] unless forward_yaml_variables?
yaml_variables.to_a.map do |hash|
- { key: hash[:key], value: ::ExpandVariables.expand(hash[:value], expand_variables) }
+ if hash[:raw] && ci_raw_variables_in_yaml_config_enabled?
+ { key: hash[:key], value: hash[:value], raw: true }
+ else
+ { key: hash[:key], value: ::ExpandVariables.expand(hash[:value], expand_variables) }
+ end
end
end
@@ -292,7 +296,11 @@ module Ci
return [] unless forward_pipeline_variables?
pipeline.variables.to_a.map do |variable|
- { key: variable.key, value: ::ExpandVariables.expand(variable.value, expand_variables) }
+ if variable.raw? && ci_raw_variables_in_yaml_config_enabled?
+ { key: variable.key, value: variable.value, raw: true }
+ else
+ { key: variable.key, value: ::ExpandVariables.expand(variable.value, expand_variables) }
+ end
end
end
@@ -301,7 +309,11 @@ module Ci
return [] unless pipeline.pipeline_schedule
pipeline.pipeline_schedule.variables.to_a.map do |variable|
- { key: variable.key, value: ::ExpandVariables.expand(variable.value, expand_variables) }
+ if variable.raw? && ci_raw_variables_in_yaml_config_enabled?
+ { key: variable.key, value: variable.value, raw: true }
+ else
+ { key: variable.key, value: ::ExpandVariables.expand(variable.value, expand_variables) }
+ end
end
end
@@ -320,6 +332,12 @@ module Ci
result.nil? ? FORWARD_DEFAULTS[:pipeline_variables] : result
end
end
+
+ def ci_raw_variables_in_yaml_config_enabled?
+ strong_memoize(:ci_raw_variables_in_yaml_config_enabled) do
+ ::Feature.enabled?(:ci_raw_variables_in_yaml_config, project)
+ end
+ end
end
end
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index a3ee8e4f364..7d89ddde0cb 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -13,10 +13,11 @@ class CommitCollection
# container - The object the commits belong to.
# commits - The Commit instances to store.
# ref - The name of the ref (e.g. "master").
- def initialize(container, commits, ref = nil)
+ def initialize(container, commits, ref = nil, page: nil, per_page: nil, count: nil)
@container = container
@commits = commits
@ref = ref
+ @pagination = Gitlab::PaginationDelegate.new(page: page, per_page: per_page, count: count)
end
def each(&block)
@@ -113,4 +114,8 @@ class CommitCollection
def method_missing(message, *args, &block)
commits.public_send(message, *args, &block)
end
+
+ def next_page
+ @pagination.next_page
+ end
end
diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb
index 1d33ff9b79a..f1d29ad5a90 100644
--- a/app/models/concerns/redis_cacheable.rb
+++ b/app/models/concerns/redis_cacheable.rb
@@ -26,7 +26,7 @@ module RedisCacheable
end
def cache_attributes(values)
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.set(cache_attribute_key, Gitlab::Json.dump(values), ex: CACHED_ATTRIBUTES_EXPIRY_TIME)
end
@@ -41,13 +41,17 @@ module RedisCacheable
def cached_attributes
strong_memoize(:cached_attributes) do
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
data = redis.get(cache_attribute_key)
Gitlab::Json.parse(data, symbolize_names: true) if data
end
end
end
+ def with_redis(&block)
+ Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def cast_value_from_cache(attribute, value)
self.class.type_for_attribute(attribute.to_s).cast(value)
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f7b57c5a442..387d492b886 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -653,8 +653,8 @@ class MergeRequest < ApplicationRecord
context_commits.count
end
- def commits(limit: nil, load_from_gitaly: false)
- return merge_request_diff.commits(limit: limit, load_from_gitaly: load_from_gitaly) if merge_request_diff.persisted?
+ def commits(limit: nil, load_from_gitaly: false, page: nil)
+ return merge_request_diff.commits(limit: limit, load_from_gitaly: load_from_gitaly, page: page) if merge_request_diff.persisted?
commits_arr = if compare_commits
reversed_commits = compare_commits.reverse
@@ -666,8 +666,8 @@ class MergeRequest < ApplicationRecord
CommitCollection.new(source_project, commits_arr, source_branch)
end
- def recent_commits(load_from_gitaly: false)
- commits(limit: MergeRequestDiff::COMMITS_SAFE_SIZE, load_from_gitaly: load_from_gitaly)
+ def recent_commits(limit: MergeRequestDiff::COMMITS_SAFE_SIZE, load_from_gitaly: false, page: nil)
+ commits(limit: limit, load_from_gitaly: load_from_gitaly, page: page)
end
def commits_count
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 9f7e98dc04b..98a9ccc2040 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -292,9 +292,9 @@ class MergeRequestDiff < ApplicationRecord
end
end
- def commits(limit: nil, load_from_gitaly: false)
- strong_memoize(:"commits_#{limit || 'all'}_#{load_from_gitaly}") do
- load_commits(limit: limit, load_from_gitaly: load_from_gitaly)
+ def commits(limit: nil, load_from_gitaly: false, page: nil)
+ strong_memoize(:"commits_#{limit || 'all'}_#{load_from_gitaly}_page_#{page}") do
+ load_commits(limit: limit, load_from_gitaly: load_from_gitaly, page: page)
end
end
@@ -725,17 +725,19 @@ class MergeRequestDiff < ApplicationRecord
end
end
- def load_commits(limit: nil, load_from_gitaly: false)
+ def load_commits(limit: nil, load_from_gitaly: false, page: nil)
+ diff_commits = page.present? ? merge_request_diff_commits.page(page).per(limit) : merge_request_diff_commits.limit(limit)
+
if load_from_gitaly
- commits = Gitlab::Git::Commit.batch_by_oid(repository, merge_request_diff_commits.limit(limit).map(&:sha))
+ commits = Gitlab::Git::Commit.batch_by_oid(repository, diff_commits.map(&:sha))
commits = Commit.decorate(commits, project)
else
- commits = merge_request_diff_commits.with_users.limit(limit)
+ commits = diff_commits.with_users
.map { |commit| Commit.from_hash(commit.to_hash, project) }
end
CommitCollection
- .new(merge_request.target_project, commits, merge_request.target_branch)
+ .new(merge_request.target_project, commits, merge_request.target_branch, page: page.to_i, per_page: limit, count: commits_count)
end
def save_diffs
diff --git a/app/models/preloaders/project_root_ancestor_preloader.rb b/app/models/preloaders/project_root_ancestor_preloader.rb
index 1e935249407..6192f79ce2c 100644
--- a/app/models/preloaders/project_root_ancestor_preloader.rb
+++ b/app/models/preloaders/project_root_ancestor_preloader.rb
@@ -9,7 +9,7 @@ module Preloaders
end
def execute
- return if @projects.is_a?(ActiveRecord::NullRelation)
+ return unless @projects.is_a?(ActiveRecord::Relation)
return unless ::Feature.enabled?(:use_traversal_ids)
root_query = Namespace.joins("INNER JOIN (#{join_sql}) as root_query ON root_query.root_id = namespaces.id")
diff --git a/app/models/preloaders/user_max_access_level_in_projects_preloader.rb b/app/models/preloaders/user_max_access_level_in_projects_preloader.rb
index 4897d90ad06..c9fd5e7718a 100644
--- a/app/models/preloaders/user_max_access_level_in_projects_preloader.rb
+++ b/app/models/preloaders/user_max_access_level_in_projects_preloader.rb
@@ -19,6 +19,8 @@ module Preloaders
end
def execute
+ return unless @user
+
project_authorizations = ProjectAuthorization.arel_table
auths = @projects
diff --git a/app/models/user.rb b/app/models/user.rb
index ad55fec0158..9b7ae453e3e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1768,7 +1768,7 @@ class User < ApplicationRecord
end
def owns_runner?(runner)
- ci_owned_runners.exists?(runner.id)
+ ci_owned_runners.include?(runner)
end
def notification_email_for(notification_group)
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 406144b7a5c..fa7b117f3cd 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -120,8 +120,6 @@ class GlobalPolicy < BasePolicy
# We can't use `read_statistics` because the user may have different permissions for different projects
rule { admin }.enable :use_project_statistics_filters
- rule { admin }.enable :delete_runners
-
rule { external_user }.prevent :create_snippet
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 9a2208b8adb..bb7c19d67eb 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -632,7 +632,6 @@ class ProjectPolicy < BasePolicy
prevent :read_commit_status
prevent :read_pipeline
prevent :read_pipeline_schedule
- prevent(*create_read_update_admin_destroy(:release))
prevent(*create_read_update_admin_destroy(:feature_flag))
prevent(:admin_feature_flags_user_lists)
end
diff --git a/app/services/ci/runners/bulk_delete_runners_service.rb b/app/services/ci/runners/bulk_delete_runners_service.rb
index ce07aa541c2..e289d8f9a8b 100644
--- a/app/services/ci/runners/bulk_delete_runners_service.rb
+++ b/app/services/ci/runners/bulk_delete_runners_service.rb
@@ -7,29 +7,69 @@ module Ci
RUNNER_LIMIT = 50
- # @param runners [Array
Push an image to the Container Registry | | | ✓ | ✓ | ✓ |
| [Container Registry](packages/container_registry/index.md):
Pull an image from the Container Registry | ✓ (*19*) | ✓ (*19*) | ✓ | ✓ | ✓ |
| [Container Registry](packages/container_registry/index.md):
Remove a Container Registry image | | | ✓ | ✓ | ✓ |
-| [GitLab Pages](project/pages/index.md):
View Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control) | ✓ | ✓ | ✓ | ✓ | ✓ |
+| [GitLab Pages](project/pages/index.md):
View Pages protected by [access control](project/pages/pages_access_control.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| [GitLab Pages](project/pages/index.md):
Manage | | | | ✓ | ✓ |
| [GitLab Pages](project/pages/index.md):
Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
| [GitLab Pages](project/pages/index.md):
Remove GitLab Pages | | | | ✓ | ✓ |
diff --git a/doc/user/project/pages/img/remove_pages_v15_3.png b/doc/user/project/pages/img/remove_pages_v15_3.png
deleted file mode 100644
index f740daf5c0b..00000000000
Binary files a/doc/user/project/pages/img/remove_pages_v15_3.png and /dev/null differ
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index ed154c0dfca..a9b8960d629 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -40,20 +40,19 @@ If you are using [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) to hos
Visit the [GitLab Pages group](https://gitlab.com/groups/pages) for a complete list of example projects. Contributions are very welcome.
-## Custom error codes Pages
+## Custom error codes pages
-You can provide your own 403 and 404 error pages by creating the `403.html` and
-`404.html` files respectively in the root directory of the `public/` directory
-that are included in the artifacts. Usually this is the root directory of
-your project, but that may differ depending on your static generator
-configuration.
+You can provide your own `403` and `404` error pages by creating `403.html` and
+`404.html` files in the root of the `public/` directory. Usually this is
+the root directory of your project, but that may differ
+depending on your static generator configuration.
If the case of `404.html`, there are different scenarios. For example:
- If you use project Pages (served under `/projectname/`) and try to access
`/projectname/non/existing_file`, GitLab Pages tries to serve first
`/projectname/404.html`, and then `/404.html`.
-- If you use user/group Pages (served under `/`) and try to access
+- If you use user or group Pages (served under `/`) and try to access
`/non/existing_file` GitLab Pages tries to serve `/404.html`.
- If you use a custom domain and try to access `/non/existing_file`, GitLab
Pages tries to serve only `/404.html`.
@@ -63,34 +62,34 @@ If the case of `404.html`, there are different scenarios. For example:
You can configure redirects for your site using a `_redirects` file. To learn more, read
the [redirects documentation](redirects.md).
-## GitLab Pages Access Control
+## Remove your pages
-To restrict access to your website, enable [GitLab Pages Access Control](pages_access_control.md).
+To remove your pages:
-## Unpublishing your Pages
-
-If you ever feel the need to purge your Pages content, you can do so by going
-to your project's settings through the gear icon in the top right, and then
-navigating to **Pages**. Select the **Remove pages** button to delete your Pages
-website.
-
-
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Pages**.
+1. Select **Remove pages**.
## Subdomains of subdomains
When using Pages under the top-level domain of a GitLab instance (`*.example.io`), you can't use HTTPS with subdomains
of subdomains. If your namespace or group name contains a dot (for example, `foo.bar`) the domain
-`https://foo.bar.example.io` does _not_ work.
+`https://foo.bar.example.io` does **not** work.
This limitation is because of the [HTTP Over TLS protocol](https://www.rfc-editor.org/rfc/rfc2818#section-3.1). HTTP pages
work as long as you don't redirect HTTP to HTTPS.
-## GitLab Pages and subgroups
+## GitLab Pages in projects and groups
-You must host your GitLab Pages website in a project. This project can belong to a [group](../../group/index.md) or
-[subgroup](../../group/subgroups/index.md). For
-[group websites](../../project/pages/getting_started_part_one.md#gitlab-pages-default-domain-names), the group must be
-at the top level and not a subgroup.
+You must host your GitLab Pages website in a project. This project can be
+[private, internal, or public](../../../user/public_access.md) and belong
+to a [group](../../group/index.md) or [subgroup](../../group/subgroups/index.md).
+
+For [group websites](../../project/pages/getting_started_part_one.md#user-and-group-website-examples),
+the group must be at the top level and not a subgroup.
+
+For [project websites](../../project/pages/getting_started_part_one.md#project-website-examples),
+you can create your project first and access it under `http(s)://namespace.example.io/projectname`.
## Specific configuration options for Pages
@@ -129,7 +128,7 @@ pages:
See this document for a [step-by-step guide](getting_started/pages_from_scratch.md).
-### `.gitlab-ci.yml` for a repository where there's also actual code
+### `.gitlab-ci.yml` for a repository with code
Remember that GitLab Pages are by default branch/tag agnostic and their
deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit
@@ -257,26 +256,6 @@ instead. Here are some examples of what happens given the above Pages site:
Note that when `public/data/index.html` exists, it takes priority over the `public/data.html` file
for both the `/data` and `/data/` URL paths.
-## Frequently Asked Questions
-
-### Can you download your generated pages?
-
-Sure. All you need to do is download the artifacts archive from the job page.
-
-### Can you use GitLab Pages if your project is private?
-
-Yes. GitLab Pages doesn't care whether you set your project's visibility level
-to private, internal or public.
-
-### Can you create a personal or a group website?
-
-Yes. See the documentation about [GitLab Pages domain names, URLs, and base URLs](getting_started_part_one.md).
-
-### Do you need to create a user/group website before creating a project website?
-
-No, you don't. You can create your project first and access it under
-`http(s)://namespace.example.io/projectname`.
-
## Known issues
For a list of known issues, visit the GitLab [public issue tracker](https://gitlab.com/gitlab-org/gitlab/-/issues?label_name[]=Category%3APages).
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 831baa9a778..19d91876892 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -66,6 +66,8 @@ module Banzai
projects = lazy { projects_for_nodes(nodes) }
project_attr = 'data-project'
+ preload_associations(projects, user)
+
nodes.select do |node|
if node.has_attribute?(project_attr)
can_read_reference?(user, projects[node], node)
@@ -261,6 +263,14 @@ module Banzai
hash[key] = {}
end
end
+
+ # For any preloading of project associations
+ # needed to avoid N+1s.
+ # Note: `projects` param is a hash of { node => project }.
+ # See #projects_for_nodes for more information.
+ def preload_associations(projects, user)
+ ::Preloaders::ProjectPolicyPreloader.new(projects.values, user).execute
+ end
end
end
end
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index 9209c9b4927..b2630a7ad7a 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -85,7 +85,7 @@ module Gitlab
end
def load_from_cache
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)
self.status = nil if self.status.empty?
@@ -93,13 +93,13 @@ module Gitlab
end
def store_in_cache
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref })
end
end
def delete_from_cache
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.del(cache_key)
end
end
@@ -107,7 +107,7 @@ module Gitlab
def has_cache?
return self.loaded unless self.loaded.nil?
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.exists?(cache_key) # rubocop:disable CodeReuse/ActiveRecord
end
end
@@ -125,6 +125,10 @@ module Gitlab
project.commit
end
end
+
+ def with_redis(&block)
+ Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
end
end
end
diff --git a/lib/gitlab/cache/import/caching.rb b/lib/gitlab/cache/import/caching.rb
index 4e7a7f326a5..5fcea8c6ae2 100644
--- a/lib/gitlab/cache/import/caching.rb
+++ b/lib/gitlab/cache/import/caching.rb
@@ -33,7 +33,7 @@ module Gitlab
# timeout - The new timeout of the key if the key is to be refreshed.
def self.read(raw_key, timeout: TIMEOUT)
key = cache_key_for(raw_key)
- value = Redis::Cache.with { |redis| redis.get(key) }
+ value = with_redis { |redis| redis.get(key) }
if value.present?
# We refresh the expiration time so frequently used keys stick
@@ -44,7 +44,7 @@ module Gitlab
# did not find a matching GitLab user. In that case we _don't_ want to
# refresh the TTL so we automatically pick up the right data when said
# user were to register themselves on the GitLab instance.
- Redis::Cache.with { |redis| redis.expire(key, timeout) }
+ with_redis { |redis| redis.expire(key, timeout) }
end
value
@@ -69,7 +69,7 @@ module Gitlab
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.set(key, value, ex: timeout)
end
@@ -85,7 +85,7 @@ module Gitlab
def self.increment(raw_key, timeout: TIMEOUT)
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
value = redis.incr(key)
redis.expire(key, timeout)
@@ -105,7 +105,7 @@ module Gitlab
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.incrby(key, value)
redis.expire(key, timeout)
end
@@ -121,7 +121,7 @@ module Gitlab
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.multi do |m|
m.sadd(key, value)
m.expire(key, timeout)
@@ -149,7 +149,7 @@ module Gitlab
def self.values_from_set(raw_key)
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.smembers(key)
end
end
@@ -160,7 +160,7 @@ module Gitlab
# key_prefix - prefix inserted before each key
# timeout - The time after which the cache key should expire.
def self.write_multiple(mapping, key_prefix: nil, timeout: TIMEOUT)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.pipelined do |multi|
mapping.each do |raw_key, value|
key = cache_key_for("#{key_prefix}#{raw_key}")
@@ -180,7 +180,7 @@ module Gitlab
def self.expire(raw_key, timeout)
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.expire(key, timeout)
end
end
@@ -199,7 +199,7 @@ module Gitlab
validate_redis_value!(value)
key = cache_key_for(raw_key)
- val = Redis::Cache.with do |redis|
+ val = with_redis do |redis|
redis
.eval(WRITE_IF_GREATER_SCRIPT, keys: [key], argv: [value, timeout])
end
@@ -218,7 +218,7 @@ module Gitlab
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.multi do |m|
m.hset(key, field, value)
m.expire(key, timeout)
@@ -232,7 +232,7 @@ module Gitlab
def self.values_from_hash(raw_key)
key = cache_key_for(raw_key)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.hgetall(key)
end
end
@@ -241,6 +241,10 @@ module Gitlab
"#{Redis::Cache::CACHE_NAMESPACE}:#{raw_key}"
end
+ def self.with_redis(&block)
+ Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def self.validate_redis_value!(value)
value_as_string = value.to_s
return if value_as_string.is_a?(String)
diff --git a/lib/gitlab/container_repository/tags/cache.rb b/lib/gitlab/container_repository/tags/cache.rb
index 47a6e67a5a1..f9de16f002f 100644
--- a/lib/gitlab/container_repository/tags/cache.rb
+++ b/lib/gitlab/container_repository/tags/cache.rb
@@ -18,7 +18,7 @@ module Gitlab
keys = tags.map(&method(:cache_key))
cached_tags_count = 0
- ::Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
tags.zip(redis.mget(keys)).each do |tag, created_at|
next unless created_at
@@ -45,7 +45,7 @@ module Gitlab
now = Time.zone.now
- ::Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
# we use a pipeline instead of a MSET because each tag has
# a specific ttl
redis.pipelined do |pipeline|
@@ -66,6 +66,10 @@ module Gitlab
def cache_key(tag)
"container_repository:{#{@container_repository.id}}:tag:#{tag.name}:created_at"
end
+
+ def with_redis(&block)
+ ::Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
end
end
end
diff --git a/lib/gitlab/diff/highlight_cache.rb b/lib/gitlab/diff/highlight_cache.rb
index 13e755bb27a..dcee3ed0c40 100644
--- a/lib/gitlab/diff/highlight_cache.rb
+++ b/lib/gitlab/diff/highlight_cache.rb
@@ -62,7 +62,7 @@ module Gitlab
end
def clear
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.del(key)
end
end
@@ -124,7 +124,7 @@ module Gitlab
# ...it will write/update a Gitlab::Redis hash (HSET)
#
def write_to_redis_hash(hash)
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.pipelined do |pipeline|
hash.each do |diff_file_id, highlighted_diff_lines_hash|
pipeline.hset(
@@ -189,7 +189,7 @@ module Gitlab
results = []
cache_key = key # Moving out redis calls for feature flags out of redis.pipelined
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.pipelined do |pipeline|
results = pipeline.hmget(cache_key, file_paths)
pipeline.expire(key, EXPIRATION)
@@ -223,6 +223,10 @@ module Gitlab
::Gitlab::Metrics::WebTransaction.current
end
+ def with_redis(&block)
+ Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def record_hit_ratio(results)
current_transaction&.increment(:gitlab_redis_diff_caching_requests_total)
current_transaction&.increment(:gitlab_redis_diff_caching_hits_total) if results.any?(&:present?)
diff --git a/lib/gitlab/discussions_diff/highlight_cache.rb b/lib/gitlab/discussions_diff/highlight_cache.rb
index 3337aeb9262..62f7f268f07 100644
--- a/lib/gitlab/discussions_diff/highlight_cache.rb
+++ b/lib/gitlab/discussions_diff/highlight_cache.rb
@@ -14,7 +14,7 @@ module Gitlab
#
# mapping - Write multiple cache values at once
def write_multiple(mapping)
- Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.multi do |multi|
mapping.each do |raw_key, value|
key = cache_key_for(raw_key)
@@ -37,7 +37,7 @@ module Gitlab
keys = raw_keys.map { |id| cache_key_for(id) }
content =
- Redis::Cache.with do |redis|
+ with_redis do |redis|
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
redis.mget(keys)
end
@@ -62,7 +62,7 @@ module Gitlab
keys = raw_keys.map { |id| cache_key_for(id) }
- Redis::Cache.with do |redis|
+ with_redis do |redis|
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
redis.del(keys)
end
@@ -78,6 +78,10 @@ module Gitlab
def cache_key_prefix
"#{Redis::Cache::CACHE_NAMESPACE}:#{VERSION}:discussion-highlight"
end
+
+ def with_redis(&block)
+ Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
end
end
end
diff --git a/lib/gitlab/external_authorization/cache.rb b/lib/gitlab/external_authorization/cache.rb
index c06711d16f8..2ba1a363421 100644
--- a/lib/gitlab/external_authorization/cache.rb
+++ b/lib/gitlab/external_authorization/cache.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def load
- @access, @reason, @refreshed_at = ::Gitlab::Redis::Cache.with do |redis|
+ @access, @reason, @refreshed_at = with_redis do |redis|
redis.hmget(cache_key, :access, :reason, :refreshed_at)
end
@@ -19,7 +19,7 @@ module Gitlab
end
def store(new_access, new_reason, new_refreshed_at)
- ::Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.pipelined do |pipeline|
pipeline.mapped_hmset(
cache_key,
@@ -58,6 +58,10 @@ module Gitlab
def cache_key
"external_authorization:user-#{@user.id}:label-#{@label}"
end
+
+ def with_redis(&block)
+ ::Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
end
end
end
diff --git a/lib/gitlab/markdown_cache/redis/store.rb b/lib/gitlab/markdown_cache/redis/store.rb
index 752ab153f37..fd5870fa842 100644
--- a/lib/gitlab/markdown_cache/redis/store.rb
+++ b/lib/gitlab/markdown_cache/redis/store.rb
@@ -28,7 +28,7 @@ module Gitlab
def save(updates)
@loaded = false
- Gitlab::Redis::Cache.with do |r|
+ with_redis do |r|
r.mapped_hmset(markdown_cache_key, updates)
r.expire(markdown_cache_key, EXPIRES_IN)
end
@@ -40,7 +40,7 @@ module Gitlab
if pipeline
pipeline.mapped_hmget(markdown_cache_key, *fields)
else
- Gitlab::Redis::Cache.with do |r|
+ with_redis do |r|
r.mapped_hmget(markdown_cache_key, *fields)
end
end
@@ -64,6 +64,10 @@ module Gitlab
"markdown_cache:#{@subject.cache_key}"
end
+
+ def with_redis(&block)
+ Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
end
end
end
diff --git a/lib/gitlab/merge_requests/mergeability/redis_interface.rb b/lib/gitlab/merge_requests/mergeability/redis_interface.rb
index f274ce1d413..1129fa639d8 100644
--- a/lib/gitlab/merge_requests/mergeability/redis_interface.rb
+++ b/lib/gitlab/merge_requests/mergeability/redis_interface.rb
@@ -7,16 +7,20 @@ module Gitlab
VERSION = 1
def save_check(merge_check:, result_hash:)
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.set(merge_check.cache_key + ":#{VERSION}", result_hash.to_json, ex: EXPIRATION)
end
end
def retrieve_check(merge_check:)
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
Gitlab::Json.parse(redis.get(merge_check.cache_key + ":#{VERSION}"), symbolize_keys: true)
end
end
+
+ def with_redis(&block)
+ Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
+ end
end
end
end
diff --git a/lib/gitlab/observability.rb b/lib/gitlab/observability.rb
new file mode 100644
index 00000000000..8dde60a73be
--- /dev/null
+++ b/lib/gitlab/observability.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Observability
+ module_function
+
+ def observability_url
+ return ENV['OVERRIDE_OBSERVABILITY_URL'] if ENV['OVERRIDE_OBSERVABILITY_URL']
+ # TODO Make observability URL configurable https://gitlab.com/gitlab-org/opstrace/opstrace-ui/-/issues/80
+ return 'https://observe.staging.gitlab.com' if Gitlab.staging?
+
+ 'https://observe.gitlab.com'
+ end
+ end
+end
diff --git a/lib/gitlab/pagination_delegate.rb b/lib/gitlab/pagination_delegate.rb
new file mode 100644
index 00000000000..05aaff5bbfc
--- /dev/null
+++ b/lib/gitlab/pagination_delegate.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class PaginationDelegate # rubocop:disable Gitlab/NamespacedClass
+ DEFAULT_PER_PAGE = Kaminari.config.default_per_page
+ MAX_PER_PAGE = Kaminari.config.max_per_page
+
+ def initialize(page:, per_page:, count:, options: {})
+ @count = count
+ @options = { default_per_page: DEFAULT_PER_PAGE,
+ max_per_page: MAX_PER_PAGE }.merge(options)
+
+ @per_page = sanitize_per_page(per_page)
+ @page = sanitize_page(page)
+ end
+
+ def total_count
+ @count
+ end
+
+ def total_pages
+ (total_count.to_f / @per_page).ceil
+ end
+
+ def next_page
+ current_page + 1 unless last_page?
+ end
+
+ def prev_page
+ current_page - 1 unless first_page?
+ end
+
+ def current_page
+ @page
+ end
+
+ def limit_value
+ @per_page
+ end
+
+ def first_page?
+ current_page == 1
+ end
+
+ def last_page?
+ current_page >= total_pages
+ end
+
+ def offset
+ (current_page - 1) * limit_value
+ end
+
+ private
+
+ def sanitize_per_page(per_page)
+ return @options[:default_per_page] unless per_page && per_page > 0
+
+ [@options[:max_per_page], per_page].min
+ end
+
+ def sanitize_page(page)
+ return 1 unless page && page > 1
+
+ [total_pages, page].min
+ end
+ end
+end
diff --git a/lib/gitlab/shard_health_cache.rb b/lib/gitlab/shard_health_cache.rb
index eeb0cc75ef9..cd1f697b1c3 100644
--- a/lib/gitlab/shard_health_cache.rb
+++ b/lib/gitlab/shard_health_cache.rb
@@ -7,14 +7,14 @@ module Gitlab
# Clears the Redis set storing the list of healthy shards
def self.clear
- Gitlab::Redis::Cache.with { |redis| redis.del(HEALTHY_SHARDS_KEY) }
+ with_redis { |redis| redis.del(HEALTHY_SHARDS_KEY) }
end
# Updates the list of healthy shards using a Redis set
#
# shards - An array of shard names to store
def self.update(shards)
- Gitlab::Redis::Cache.with do |redis|
+ with_redis do |redis|
redis.multi do |m|
m.del(HEALTHY_SHARDS_KEY)
shards.each { |shard_name| m.sadd(HEALTHY_SHARDS_KEY, shard_name) }
@@ -25,19 +25,23 @@ module Gitlab
# Returns an array of strings of healthy shards
def self.cached_healthy_shards
- Gitlab::Redis::Cache.with { |redis| redis.smembers(HEALTHY_SHARDS_KEY) }
+ with_redis { |redis| redis.smembers(HEALTHY_SHARDS_KEY) }
end
# Checks whether the given shard name is in the list of healthy shards.
#
# shard_name - The string to check
def self.healthy_shard?(shard_name)
- Gitlab::Redis::Cache.with { |redis| redis.sismember(HEALTHY_SHARDS_KEY, shard_name) }
+ with_redis { |redis| redis.sismember(HEALTHY_SHARDS_KEY, shard_name) }
end
# Returns the number of healthy shards in the Redis set
def self.healthy_shard_count
- Gitlab::Redis::Cache.with { |redis| redis.scard(HEALTHY_SHARDS_KEY) }
+ with_redis { |redis| redis.scard(HEALTHY_SHARDS_KEY) }
+ end
+
+ def self.with_redis(&block)
+ Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
index 0fa38521657..357e9d41187 100644
--- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
+++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
@@ -21,22 +21,8 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
DEFAULT_DUPLICATE_KEY_TTL = 6.hours
- WAL_LOCATION_TTL = 60.seconds
DEFAULT_STRATEGY = :until_executing
STRATEGY_NONE = :none
- DEDUPLICATED_FLAG_VALUE = 1
-
- LUA_SET_WAL_SCRIPT = <<~EOS
- local key, wal, offset, ttl = KEYS[1], ARGV[1], tonumber(ARGV[2]), ARGV[3]
- local existing_offset = redis.call("LINDEX", key, -1)
- if existing_offset == false then
- redis.call("RPUSH", key, wal, offset)
- redis.call("EXPIRE", key, ttl)
- elseif offset > tonumber(existing_offset) then
- redis.call("LSET", key, 0, wal)
- redis.call("LSET", key, -1, offset)
- end
- EOS
attr_reader :existing_jid
@@ -60,129 +46,6 @@ module Gitlab
# This method will return the jid that was set in redis
def check!(expiry = duplicate_key_ttl)
- if Feature.enabled?(:duplicate_jobs_cookie)
- check_cookie!(expiry)
- else
- check_multi!(expiry)
- end
- end
-
- def update_latest_wal_location!
- return unless job_wal_locations.present?
-
- if Feature.enabled?(:duplicate_jobs_cookie)
- update_latest_wal_location_cookie!
- else
- update_latest_wal_location_multi!
- end
- end
-
- def latest_wal_locations
- return {} unless job_wal_locations.present?
-
- strong_memoize(:latest_wal_locations) do
- if Feature.enabled?(:duplicate_jobs_cookie)
- get_cookie.fetch('wal_locations', {})
- else
- latest_wal_locations_multi
- end
- end
- end
-
- def delete!
- if Feature.enabled?(:duplicate_jobs_cookie)
- with_redis { |redis| redis.del(cookie_key) }
- else
- Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- with_redis do |redis|
- redis.multi do |multi|
- multi.del(idempotency_key, deduplicated_flag_key)
- delete_wal_locations!(multi)
- end
- end
- end
- end
- end
-
- def reschedule
- Gitlab::SidekiqLogging::DeduplicationLogger.instance.rescheduled_log(job)
-
- worker_klass.perform_async(*arguments)
- end
-
- def scheduled?
- scheduled_at.present?
- end
-
- def duplicate?
- raise "Call `#check!` first to check for existing duplicates" unless existing_jid
-
- jid != existing_jid
- end
-
- def set_deduplicated_flag!(expiry = duplicate_key_ttl)
- return unless reschedulable?
-
- if Feature.enabled?(:duplicate_jobs_cookie)
- with_redis { |redis| redis.eval(DEDUPLICATED_SCRIPT, keys: [cookie_key]) }
- else
- with_redis do |redis|
- redis.set(deduplicated_flag_key, DEDUPLICATED_FLAG_VALUE, ex: expiry, nx: true)
- end
- end
- end
-
- DEDUPLICATED_SCRIPT = <<~LUA
- local cookie_msgpack = redis.call("get", KEYS[1])
- if not cookie_msgpack then
- return
- end
- local cookie = cmsgpack.unpack(cookie_msgpack)
- cookie.deduplicated = "1"
- redis.call("set", KEYS[1], cmsgpack.pack(cookie), "ex", redis.call("ttl", KEYS[1]))
- LUA
-
- def should_reschedule?
- return false unless reschedulable?
-
- if Feature.enabled?(:duplicate_jobs_cookie)
- get_cookie['deduplicated'].present?
- else
- with_redis do |redis|
- redis.get(deduplicated_flag_key).present?
- end
- end
- end
-
- def scheduled_at
- job['at']
- end
-
- def options
- return {} unless worker_klass
- return {} unless worker_klass.respond_to?(:get_deduplication_options)
-
- worker_klass.get_deduplication_options
- end
-
- def idempotent?
- return false unless worker_klass
- return false unless worker_klass.respond_to?(:idempotent?)
-
- worker_klass.idempotent?
- end
-
- def duplicate_key_ttl
- options[:ttl] || DEFAULT_DUPLICATE_KEY_TTL
- end
-
- private
-
- attr_writer :existing_wal_locations
- attr_reader :queue_name, :job
- attr_writer :existing_jid
-
- def check_cookie!(expiry)
my_cookie = {
'jid' => jid,
'offsets' => {},
@@ -206,26 +69,9 @@ module Gitlab
self.existing_jid = actual_cookie['jid']
end
- def check_multi!(expiry)
- read_jid = nil
- read_wal_locations = {}
+ def update_latest_wal_location!
+ return unless job_wal_locations.present?
- with_redis do |redis|
- redis.multi do |multi|
- multi.set(idempotency_key, jid, ex: expiry, nx: true)
- read_wal_locations = check_existing_wal_locations!(multi, expiry)
- read_jid = multi.get(idempotency_key)
- end
- end
-
- job['idempotency_key'] = idempotency_key
-
- # We need to fetch values since the read_wal_locations and read_jid were obtained inside transaction, under redis.multi command.
- self.existing_wal_locations = read_wal_locations.transform_values(&:value)
- self.existing_jid = read_jid.value
- end
-
- def update_latest_wal_location_cookie!
argv = []
job_wal_locations.each do |connection_name, location|
argv += [connection_name, pg_wal_lsn_diff(connection_name), location]
@@ -260,56 +106,86 @@ module Gitlab
redis.call("set", KEYS[1], cmsgpack.pack(cookie), "ex", redis.call("ttl", KEYS[1]))
LUA
- def update_latest_wal_location_multi!
- with_redis do |redis|
- redis.multi do |multi|
- job_wal_locations.each do |connection_name, location|
- multi.eval(
- LUA_SET_WAL_SCRIPT,
- keys: [wal_location_key(connection_name)],
- argv: [location, pg_wal_lsn_diff(connection_name).to_i, WAL_LOCATION_TTL]
- )
- end
- end
+ def latest_wal_locations
+ return {} unless job_wal_locations.present?
+
+ strong_memoize(:latest_wal_locations) do
+ get_cookie.fetch('wal_locations', {})
end
end
- def latest_wal_locations_multi
- read_wal_locations = {}
-
- with_redis do |redis|
- redis.multi do |multi|
- job_wal_locations.keys.each do |connection_name|
- read_wal_locations[connection_name] = multi.lindex(wal_location_key(connection_name), 0)
- end
- end
- end
- read_wal_locations.transform_values(&:value).compact
+ def delete!
+ with_redis { |redis| redis.del(cookie_key) }
end
+ def reschedule
+ Gitlab::SidekiqLogging::DeduplicationLogger.instance.rescheduled_log(job)
+
+ worker_klass.perform_async(*arguments)
+ end
+
+ def scheduled?
+ scheduled_at.present?
+ end
+
+ def duplicate?
+ raise "Call `#check!` first to check for existing duplicates" unless existing_jid
+
+ jid != existing_jid
+ end
+
+ def set_deduplicated_flag!(expiry = duplicate_key_ttl)
+ return unless reschedulable?
+
+ with_redis { |redis| redis.eval(DEDUPLICATED_SCRIPT, keys: [cookie_key]) }
+ end
+
+ DEDUPLICATED_SCRIPT = <<~LUA
+ local cookie_msgpack = redis.call("get", KEYS[1])
+ if not cookie_msgpack then
+ return
+ end
+ local cookie = cmsgpack.unpack(cookie_msgpack)
+ cookie.deduplicated = "1"
+ redis.call("set", KEYS[1], cmsgpack.pack(cookie), "ex", redis.call("ttl", KEYS[1]))
+ LUA
+
+ def should_reschedule?
+ reschedulable? && get_cookie['deduplicated'].present?
+ end
+
+ def scheduled_at
+ job['at']
+ end
+
+ def options
+ return {} unless worker_klass
+ return {} unless worker_klass.respond_to?(:get_deduplication_options)
+
+ worker_klass.get_deduplication_options
+ end
+
+ def idempotent?
+ return false unless worker_klass
+ return false unless worker_klass.respond_to?(:idempotent?)
+
+ worker_klass.idempotent?
+ end
+
+ def duplicate_key_ttl
+ options[:ttl] || DEFAULT_DUPLICATE_KEY_TTL
+ end
+
+ private
+
+ attr_writer :existing_wal_locations
+ attr_reader :queue_name, :job
+ attr_writer :existing_jid
+
def worker_klass
@worker_klass ||= worker_class_name.to_s.safe_constantize
end
- def delete_wal_locations!(redis)
- job_wal_locations.keys.each do |connection_name|
- redis.del(wal_location_key(connection_name))
- redis.del(existing_wal_location_key(connection_name))
- end
- end
-
- def check_existing_wal_locations!(redis, expiry)
- read_wal_locations = {}
-
- job_wal_locations.each do |connection_name, location|
- key = existing_wal_location_key(connection_name)
- redis.set(key, location, ex: expiry, nx: true)
- read_wal_locations[connection_name] = redis.get(key)
- end
-
- read_wal_locations
- end
-
def job_wal_locations
job['wal_locations'] || {}
end
@@ -343,14 +219,6 @@ module Gitlab
job['jid']
end
- def existing_wal_location_key(connection_name)
- "#{idempotency_key}:#{connection_name}:existing_wal_location"
- end
-
- def wal_location_key(connection_name)
- "#{idempotency_key}:#{connection_name}:wal_location"
- end
-
def cookie_key
"#{idempotency_key}:cookie:v2"
end
@@ -363,10 +231,6 @@ module Gitlab
@idempotency_key ||= job['idempotency_key'] || "#{namespace}:#{idempotency_hash}"
end
- def deduplicated_flag_key
- "#{idempotency_key}:deduplicate_flag"
- end
-
def idempotency_hash
Digest::SHA256.hexdigest(idempotency_string)
end
diff --git a/lib/gitlab/usage/metrics/name_suggestion.rb b/lib/gitlab/usage/metrics/name_suggestion.rb
index 238a7a51a20..44723b6f3d4 100644
--- a/lib/gitlab/usage/metrics/name_suggestion.rb
+++ b/lib/gitlab/usage/metrics/name_suggestion.rb
@@ -7,6 +7,7 @@ module Gitlab
FREE_TEXT_METRIC_NAME = "