Commit c2060944 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 596dfee5 5cc0b39e
<script> <script>
import { __, sprintf } from '~/locale'; import { GlSprintf, GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
const alertMessage = __(
'Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs.',
);
export default { export default {
alertMessage,
components: {
GlSprintf,
GlLink,
},
computed: { computed: {
currentPath() { currentPath() {
return window.location.pathname; return window.location.pathname;
}, },
alertMessage() {
return sprintf(
__(
'Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs.',
),
{
linkStart: `<a href="${this.currentPath}" target="_blank" rel="nofollow">`,
linkEnd: `</a>`,
},
false,
);
},
}, },
}; };
</script> </script>
<template> <template>
<div <div class="alert alert-danger">
class="alert alert-danger" <gl-sprintf :message="$options.alertMessage">
v-html="alertMessage /* eslint-disable-line vue/no-v-html */" <template #link="{ content }">
></div> <gl-link :href="currentPath" target="_blank" rel="nofollow">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</div>
</template> </template>
...@@ -76,6 +76,7 @@ ...@@ -76,6 +76,7 @@
} }
.dropdown-toggle, .dropdown-toggle,
.dropdown-menu-toggle,
.confidential-merge-request-fork-group .dropdown-toggle { .confidential-merge-request-fork-group .dropdown-toggle {
padding: 6px 8px 6px 10px; padding: 6px 8px 6px 10px;
background-color: $white; background-color: $white;
...@@ -131,7 +132,6 @@ ...@@ -131,7 +132,6 @@
// This is double classed to solve a specificity issue with the gitlab ui buttons // This is double classed to solve a specificity issue with the gitlab ui buttons
.dropdown-menu-toggle.dropdown-menu-toggle { .dropdown-menu-toggle.dropdown-menu-toggle {
@extend .dropdown-toggle;
justify-content: flex-start; justify-content: flex-start;
overflow: hidden; overflow: hidden;
padding-right: 25px; padding-right: 25px;
......
...@@ -198,22 +198,6 @@ h1 { ...@@ -198,22 +198,6 @@ h1 {
.dropdown { .dropdown {
position: relative; position: relative;
} }
.dropdown-menu-toggle {
white-space: nowrap;
}
.dropdown-menu-toggle::after {
display: inline-block;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;
border-left: 0.3em solid transparent;
}
.dropdown-menu-toggle:empty::after {
margin-left: 0;
}
.dropdown-menu { .dropdown-menu {
position: absolute; position: absolute;
top: 100%; top: 100%;
...@@ -466,9 +450,6 @@ a { ...@@ -466,9 +450,6 @@ a {
.hide { .hide {
display: none; display: none;
} }
.dropdown-menu-toggle::after {
display: none;
}
.badge:not(.gl-badge) { .badge:not(.gl-badge) {
padding: 4px 5px; padding: 4px 5px;
font-size: 12px; font-size: 12px;
...@@ -548,7 +529,7 @@ body { ...@@ -548,7 +529,7 @@ body {
border-radius: 0.25rem; border-radius: 0.25rem;
white-space: nowrap; white-space: nowrap;
} }
.no-outline.dropdown-menu-toggle { .dropdown-menu-toggle.no-outline {
outline: 0; outline: 0;
} }
.dropdown-menu-toggle.dropdown-menu-toggle { .dropdown-menu-toggle.dropdown-menu-toggle {
......
...@@ -178,22 +178,6 @@ h1 { ...@@ -178,22 +178,6 @@ h1 {
.dropdown { .dropdown {
position: relative; position: relative;
} }
.dropdown-menu-toggle {
white-space: nowrap;
}
.dropdown-menu-toggle::after {
display: inline-block;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;
border-left: 0.3em solid transparent;
}
.dropdown-menu-toggle:empty::after {
margin-left: 0;
}
.dropdown-menu { .dropdown-menu {
position: absolute; position: absolute;
top: 100%; top: 100%;
...@@ -446,9 +430,6 @@ a { ...@@ -446,9 +430,6 @@ a {
.hide { .hide {
display: none; display: none;
} }
.dropdown-menu-toggle::after {
display: none;
}
.badge:not(.gl-badge) { .badge:not(.gl-badge) {
padding: 4px 5px; padding: 4px 5px;
font-size: 12px; font-size: 12px;
...@@ -528,7 +509,7 @@ body { ...@@ -528,7 +509,7 @@ body {
border-radius: 0.25rem; border-radius: 0.25rem;
white-space: nowrap; white-space: nowrap;
} }
.no-outline.dropdown-menu-toggle { .dropdown-menu-toggle.no-outline {
outline: 0; outline: 0;
} }
.dropdown-menu-toggle.dropdown-menu-toggle { .dropdown-menu-toggle.dropdown-menu-toggle {
......
...@@ -36,7 +36,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -36,7 +36,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def starred def starred
@projects = load_projects(params.merge(starred: true)) @projects = load_projects(params.merge(starred: true))
.includes(:forked_from_project, :topics, :topics_acts_as_taggable) .includes(:forked_from_project, :topics)
@groups = [] @groups = []
......
...@@ -182,8 +182,8 @@ class ProjectsFinder < UnionFinder ...@@ -182,8 +182,8 @@ class ProjectsFinder < UnionFinder
def by_topics(items) def by_topics(items)
return items unless params[:topic].present? return items unless params[:topic].present?
topics = params[:topic].instance_of?(String) ? params[:topic].strip.split(/\s*,\s*/) : params[:topic] topics = params[:topic].instance_of?(String) ? params[:topic].split(',') : params[:topic]
topics.each do |topic| topics.map(&:strip).uniq.reject(&:empty?).each do |topic|
items = items.with_topic(topic) items = items.with_topic(topic)
end end
......
...@@ -400,7 +400,8 @@ module ApplicationSettingsHelper ...@@ -400,7 +400,8 @@ module ApplicationSettingsHelper
:user_deactivation_emails_enabled, :user_deactivation_emails_enabled,
:sidekiq_job_limiter_mode, :sidekiq_job_limiter_mode,
:sidekiq_job_limiter_compression_threshold_bytes, :sidekiq_job_limiter_compression_threshold_bytes,
:sidekiq_job_limiter_limit_bytes :sidekiq_job_limiter_limit_bytes,
:suggest_pipeline_enabled
].tap do |settings| ].tap do |settings|
settings << :deactivate_dormant_users unless Gitlab.com? settings << :deactivate_dormant_users unless Gitlab.com?
end end
......
...@@ -159,6 +159,7 @@ module ApplicationSettingImplementation ...@@ -159,6 +159,7 @@ module ApplicationSettingImplementation
spam_check_endpoint_enabled: false, spam_check_endpoint_enabled: false,
spam_check_endpoint_url: nil, spam_check_endpoint_url: nil,
spam_check_api_key: nil, spam_check_api_key: nil,
suggest_pipeline_enabled: true,
terminal_max_session_time: 0, terminal_max_session_time: 0,
throttle_authenticated_api_enabled: false, throttle_authenticated_api_enabled: false,
throttle_authenticated_api_period_in_seconds: 3600, throttle_authenticated_api_period_in_seconds: 3600,
......
...@@ -128,26 +128,9 @@ class Project < ApplicationRecord ...@@ -128,26 +128,9 @@ class Project < ApplicationRecord
after_initialize :use_hashed_storage after_initialize :use_hashed_storage
after_create :check_repository_absence! after_create :check_repository_absence!
# Required during the `ActsAsTaggableOn::Tag -> Topic` migration
# TODO: remove 'acts_as_ordered_taggable_on' and ':topics_acts_as_taggable' in the further process of the migration
# https://gitlab.com/gitlab-org/gitlab/-/issues/335946
acts_as_ordered_taggable_on :topics
has_many :topics_acts_as_taggable, -> { order("#{ActsAsTaggableOn::Tagging.table_name}.id") },
class_name: 'ActsAsTaggableOn::Tag',
through: :topic_taggings,
source: :tag
has_many :project_topics, -> { order(:id) }, class_name: 'Projects::ProjectTopic' has_many :project_topics, -> { order(:id) }, class_name: 'Projects::ProjectTopic'
has_many :topics, through: :project_topics, class_name: 'Projects::Topic' has_many :topics, through: :project_topics, class_name: 'Projects::Topic'
# Required during the `ActsAsTaggableOn::Tag -> Topic` migration
# TODO: remove 'topics' in the further process of the migration
# https://gitlab.com/gitlab-org/gitlab/-/issues/335946
alias_method :topics_new, :topics
def topics
self.topics_acts_as_taggable + self.topics_new
end
attr_accessor :old_path_with_namespace attr_accessor :old_path_with_namespace
attr_accessor :template_name attr_accessor :template_name
attr_writer :pipeline_status attr_writer :pipeline_status
...@@ -653,15 +636,8 @@ class Project < ApplicationRecord ...@@ -653,15 +636,8 @@ class Project < ApplicationRecord
scope :with_topic, ->(topic_name) do scope :with_topic, ->(topic_name) do
topic = Projects::Topic.find_by_name(topic_name) topic = Projects::Topic.find_by_name(topic_name)
acts_as_taggable_on_topic = ActsAsTaggableOn::Tag.find_by_name(topic_name)
return none unless topic || acts_as_taggable_on_topic topic ? where(id: topic.project_topics.select(:project_id)) : none
relations = []
relations << where(id: topic.project_topics.select(:project_id)) if topic
relations << where(id: acts_as_taggable_on_topic.taggings.select(:taggable_id)) if acts_as_taggable_on_topic
Project.from_union(relations)
end end
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
...@@ -679,7 +655,7 @@ class Project < ApplicationRecord ...@@ -679,7 +655,7 @@ class Project < ApplicationRecord
mount_uploader :bfg_object_map, AttachmentUploader mount_uploader :bfg_object_map, AttachmentUploader
def self.with_api_entity_associations def self.with_api_entity_associations
preload(:project_feature, :route, :topics, :topics_acts_as_taggable, :group, :timelogs, namespace: [:route, :owner]) preload(:project_feature, :route, :topics, :group, :timelogs, namespace: [:route, :owner])
end end
def self.with_web_entity_associations def self.with_web_entity_associations
...@@ -2735,15 +2711,9 @@ class Project < ApplicationRecord ...@@ -2735,15 +2711,9 @@ class Project < ApplicationRecord
@topic_list = @topic_list.split(',') if @topic_list.instance_of?(String) @topic_list = @topic_list.split(',') if @topic_list.instance_of?(String)
@topic_list = @topic_list.map(&:strip).uniq.reject(&:empty?) @topic_list = @topic_list.map(&:strip).uniq.reject(&:empty?)
if @topic_list != self.topic_list || self.topics_acts_as_taggable.any? if @topic_list != self.topic_list
self.topics_new.delete_all self.topics.delete_all
self.topics = @topic_list.map { |topic| Projects::Topic.find_or_create_by(name: topic) } self.topics = @topic_list.map { |topic| Projects::Topic.find_or_create_by(name: topic) }
# Remove old topics (ActsAsTaggableOn::Tag)
# Required during the `ActsAsTaggableOn::Tag -> Topic` migration
# TODO: remove in the further process of the migration
# https://gitlab.com/gitlab-org/gitlab/-/issues/335946
self.topic_taggings.clear
end end
@topic_list = nil @topic_list = nil
......
...@@ -24,7 +24,7 @@ module Search ...@@ -24,7 +24,7 @@ module Search
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def projects def projects
@projects ||= ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute.preload(:topics, :taggings) @projects ||= ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute.preload(:topics, :project_topics)
end end
def allowed_scopes def allowed_scopes
......
...@@ -69,5 +69,12 @@ ...@@ -69,5 +69,12 @@
%p.form-text.text-muted %p.form-text.text-muted
= _("The default CI/CD configuration file and path for new projects.").html_safe = _("The default CI/CD configuration file and path for new projects.").html_safe
= link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank' = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank'
.form-group
.form-check
= f.check_box :suggest_pipeline_enabled, class: 'form-check-input'
= f.label :suggest_pipeline_enabled, class: 'form-check-label' do
= s_('AdminSettings|Enable pipeline suggestion banner')
.form-text.text-muted
= s_('AdminSettings|Display a banner on merge requests in projects with no pipelines to initiate steps to add a .gitlab-ci.yml file.')
= f.submit _('Save changes'), class: "gl-button btn btn-confirm" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"
# frozen_string_literal: true
class RemoveTemporaryIndexForProjectTopicsOnTaggings < Gitlab::Database::Migration[1.0]
MIGRATION = 'ExtractProjectTopicsIntoSeparateTable'
INDEX_NAME = 'tmp_index_taggings_on_id_where_taggable_type_project'
INDEX_CONDITION = "taggable_type = 'Project'"
disable_ddl_transaction!
def up
# Ensure that no background jobs of 20210730104800_schedule_extract_project_topics_into_separate_table remain
finalize_background_migration MIGRATION
# this index was used in 20210730104800_schedule_extract_project_topics_into_separate_table
remove_concurrent_index_by_name :taggings, INDEX_NAME
end
def down
add_concurrent_index :taggings, :id, where: INDEX_CONDITION, name: INDEX_NAME # rubocop:disable Migration/PreventIndexCreation
end
end
a0ba9fb9e2f7f738926a2273f9ff644c43acb999f4d27adf192e5006582a2a0e
\ No newline at end of file
...@@ -26916,8 +26916,6 @@ CREATE INDEX tmp_index_namespaces_empty_traversal_ids_with_root_namespaces ON na ...@@ -26916,8 +26916,6 @@ CREATE INDEX tmp_index_namespaces_empty_traversal_ids_with_root_namespaces ON na
CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2); CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2);
CREATE INDEX tmp_index_taggings_on_id_where_taggable_type_project ON taggings USING btree (id) WHERE ((taggable_type)::text = 'Project'::text);
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name); CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name);
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_components_on_distribution_id_and_name ON packages_debian_group_components USING btree (distribution_id, name); CREATE UNIQUE INDEX uniq_pkgs_deb_grp_components_on_distribution_id_and_name ON packages_debian_group_components USING btree (distribution_id, name);
...@@ -14,7 +14,8 @@ Even though Git is very resilient and tries to prevent data integrity issues, ...@@ -14,7 +14,8 @@ Even though Git is very resilient and tries to prevent data integrity issues,
there are times when things go wrong. The following Rake tasks intend to there are times when things go wrong. The following Rake tasks intend to
help GitLab administrators diagnose problem repositories so they can be fixed. help GitLab administrators diagnose problem repositories so they can be fixed.
There are 3 things that are checked to determine integrity. These Rake tasks use three different methods to determine the integrity of Git
repositories.
1. Git repository file system check ([`git fsck`](https://git-scm.com/docs/git-fsck)). 1. Git repository file system check ([`git fsck`](https://git-scm.com/docs/git-fsck)).
This step verifies the connectivity and validity of objects in the repository. This step verifies the connectivity and validity of objects in the repository.
...@@ -37,7 +38,7 @@ exactly which repositories are causing the trouble. ...@@ -37,7 +38,7 @@ exactly which repositories are causing the trouble.
### Check project code repositories ### Check project code repositories
This task loops through the project code repositories and runs the integrity check This task loops through the project code repositories and runs the integrity check
described previously. If a project uses a pool repository, that will also be checked. described previously. If a project uses a pool repository, that is also checked.
Other types of Git repositories [are not checked](https://gitlab.com/gitlab-org/gitaly/-/issues/3643). Other types of Git repositories [are not checked](https://gitlab.com/gitlab-org/gitaly/-/issues/3643).
**Omnibus Installation** **Omnibus Installation**
...@@ -67,7 +68,7 @@ source repository. ...@@ -67,7 +68,7 @@ source repository.
This task loops through all repositories on the GitLab server and outputs This task loops through all repositories on the GitLab server and outputs
checksums in the format `<PROJECT ID>,<CHECKSUM>`. checksums in the format `<PROJECT ID>,<CHECKSUM>`.
- If a repository doesn't exist, the project ID will have a blank checksum. - If a repository doesn't exist, the project ID is a blank checksum.
- If a repository exists but is empty, the output checksum is `0000000000000000000000000000000000000000`. - If a repository exists but is empty, the output checksum is `0000000000000000000000000000000000000000`.
- Projects which don't exist are skipped. - Projects which don't exist are skipped.
...@@ -85,9 +86,9 @@ sudo -u git -H bundle exec rake gitlab:git:checksum_projects RAILS_ENV=productio ...@@ -85,9 +86,9 @@ sudo -u git -H bundle exec rake gitlab:git:checksum_projects RAILS_ENV=productio
For example, if: For example, if:
- Project with ID#2 doesn't exist, it will be skipped. - Project with ID#2 doesn't exist, it is skipped.
- Project with ID#4 doesn't have a repository, its checksum will be blank. - Project with ID#4 doesn't have a repository, its checksum is blank.
- Project with ID#5 has an empty repository, its checksum will be `0000000000000000000000000000000000000000`. - Project with ID#5 has an empty repository, its checksum is `0000000000000000000000000000000000000000`.
The output would then look something like: The output would then look something like:
...@@ -115,7 +116,7 @@ These integrity checks can detect missing files. Additionally, for locally ...@@ -115,7 +116,7 @@ These integrity checks can detect missing files. Additionally, for locally
stored files, checksums are generated and stored in the database upon upload, stored files, checksums are generated and stored in the database upon upload,
and these checks verify them against current files. and these checks verify them against current files.
Currently, integrity checks are supported for the following types of file: Integrity checks are supported for the following types of file:
- CI artifacts (Available from version 10.7.0) - CI artifacts (Available from version 10.7.0)
- LFS objects (Available from version 10.6.0) - LFS objects (Available from version 10.6.0)
......
...@@ -216,6 +216,20 @@ of your GitLab instance (`.gitlab-ci.yml` if not set): ...@@ -216,6 +216,20 @@ of your GitLab instance (`.gitlab-ci.yml` if not set):
It is also possible to specify a [custom CI/CD configuration file for a specific project](../../../ci/pipelines/settings.md#specify-a-custom-cicd-configuration-file). It is also possible to specify a [custom CI/CD configuration file for a specific project](../../../ci/pipelines/settings.md#specify-a-custom-cicd-configuration-file).
## Enable or disable the pipeline suggestion banner
By default, a banner displays in merge requests with no pipeline suggesting a
walkthrough on how to add one.
![Suggest pipeline banner](img/suggest_pipeline_banner.png)
To enable or disable the banner:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > CI/CD**.
1. Select or clear the **Enable pipeline suggestion banner** checkbox.
1. Select **Save changes**.
## Required pipeline configuration **(PREMIUM SELF)** ## Required pipeline configuration **(PREMIUM SELF)**
WARNING: WARNING:
......
...@@ -16,7 +16,7 @@ export default function initTestCaseShow({ mountPointSelector }) { ...@@ -16,7 +16,7 @@ export default function initTestCaseShow({ mountPointSelector }) {
} }
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(), defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
}); });
const sidebarOptions = JSON.parse(el.dataset.sidebarOptions); const sidebarOptions = JSON.parse(el.dataset.sidebarOptions);
......
...@@ -198,22 +198,6 @@ h1 { ...@@ -198,22 +198,6 @@ h1 {
.dropdown { .dropdown {
position: relative; position: relative;
} }
.dropdown-menu-toggle {
white-space: nowrap;
}
.dropdown-menu-toggle::after {
display: inline-block;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;
border-left: 0.3em solid transparent;
}
.dropdown-menu-toggle:empty::after {
margin-left: 0;
}
.dropdown-menu { .dropdown-menu {
position: absolute; position: absolute;
top: 100%; top: 100%;
...@@ -466,9 +450,6 @@ a { ...@@ -466,9 +450,6 @@ a {
.hide { .hide {
display: none; display: none;
} }
.dropdown-menu-toggle::after {
display: none;
}
.badge:not(.gl-badge) { .badge:not(.gl-badge) {
padding: 4px 5px; padding: 4px 5px;
font-size: 12px; font-size: 12px;
...@@ -548,7 +529,7 @@ body { ...@@ -548,7 +529,7 @@ body {
border-radius: 0.25rem; border-radius: 0.25rem;
white-space: nowrap; white-space: nowrap;
} }
.no-outline.dropdown-menu-toggle { .dropdown-menu-toggle.no-outline {
outline: 0; outline: 0;
} }
.dropdown-menu-toggle.dropdown-menu-toggle { .dropdown-menu-toggle.dropdown-menu-toggle {
......
...@@ -178,22 +178,6 @@ h1 { ...@@ -178,22 +178,6 @@ h1 {
.dropdown { .dropdown {
position: relative; position: relative;
} }
.dropdown-menu-toggle {
white-space: nowrap;
}
.dropdown-menu-toggle::after {
display: inline-block;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;
border-left: 0.3em solid transparent;
}
.dropdown-menu-toggle:empty::after {
margin-left: 0;
}
.dropdown-menu { .dropdown-menu {
position: absolute; position: absolute;
top: 100%; top: 100%;
...@@ -446,9 +430,6 @@ a { ...@@ -446,9 +430,6 @@ a {
.hide { .hide {
display: none; display: none;
} }
.dropdown-menu-toggle::after {
display: none;
}
.badge:not(.gl-badge) { .badge:not(.gl-badge) {
padding: 4px 5px; padding: 4px 5px;
font-size: 12px; font-size: 12px;
...@@ -528,7 +509,7 @@ body { ...@@ -528,7 +509,7 @@ body {
border-radius: 0.25rem; border-radius: 0.25rem;
white-space: nowrap; white-space: nowrap;
} }
.no-outline.dropdown-menu-toggle { .dropdown-menu-toggle.no-outline {
outline: 0; outline: 0;
} }
.dropdown-menu-toggle.dropdown-menu-toggle { .dropdown-menu-toggle.dropdown-menu-toggle {
......
...@@ -11,8 +11,6 @@ module Ci ...@@ -11,8 +11,6 @@ module Ci
idempotent! idempotent!
def perform(from_id, to_id) def perform(from_id, to_id)
return unless Feature.enabled?(:ci_parallel_minutes_reset, default_enabled: true)
::Ci::Minutes::BatchResetService.new.execute!(ids_range: (from_id..to_id)) ::Ci::Minutes::BatchResetService.new.execute!(ids_range: (from_id..to_id))
end end
end end
......
...@@ -17,34 +17,21 @@ class ClearSharedRunnersMinutesWorker # rubocop:disable Scalability/IdempotentWo ...@@ -17,34 +17,21 @@ class ClearSharedRunnersMinutesWorker # rubocop:disable Scalability/IdempotentWo
BATCH_SIZE = 100_000 BATCH_SIZE = 100_000
def perform def perform
if Feature.enabled?(:ci_parallel_minutes_reset, default_enabled: true) start_id = Namespace.minimum(:id)
start_id = Namespace.minimum(:id) last_id = Namespace.maximum(:id)
last_id = Namespace.maximum(:id)
batches = [(last_id - start_id) / BATCH_SIZE, 1].max batches = [(last_id - start_id) / BATCH_SIZE, 1].max
execution_offset = (TIME_SPREAD / batches).to_i execution_offset = (TIME_SPREAD / batches).to_i
(start_id..last_id).step(BATCH_SIZE).with_index do |batch_start_id, batch_index| (start_id..last_id).step(BATCH_SIZE).with_index do |batch_start_id, batch_index|
batch_end_id = batch_start_id + BATCH_SIZE - 1 batch_end_id = batch_start_id + BATCH_SIZE - 1
delay = execution_offset * batch_index delay = execution_offset * batch_index
# #perform_in is used instead of #perform_async to spread the load # #perform_in is used instead of #perform_async to spread the load
# evenly accross the first three hours of the month to avoid stressing # evenly accross the first three hours of the month to avoid stressing
# the database. # the database.
Ci::BatchResetMinutesWorker.perform_in(delay, batch_start_id, batch_end_id) Ci::BatchResetMinutesWorker.perform_in(delay, batch_start_id, batch_end_id)
end
else
return unless try_obtain_lease
Ci::Minutes::BatchResetService.new.execute!
end end
end end
private
def try_obtain_lease
Gitlab::ExclusiveLease.new('gitlab_clear_shared_runners_minutes_worker',
timeout: LEASE_TIMEOUT).try_obtain
end
end end
---
name: ci_parallel_minutes_reset
introduced_by_url:
rollout_issue_url:
milestone: '12.10'
type: development
group: group::pipeline execution
default_enabled: true
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
case scope case scope
when 'projects' when 'projects'
eager_load(projects, page, per_page, preload_method, [:route, :namespace, :topics, :topics_acts_as_taggable]) eager_load(projects, page, per_page, preload_method, [:route, :namespace, :topics])
when 'issues' when 'issues'
eager_load(issues, page, per_page, preload_method, project: [:route, :namespace], labels: [], timelogs: [], assignees: []) eager_load(issues, page, per_page, preload_method, project: [:route, :namespace], labels: [], timelogs: [], assignees: [])
when 'merge_requests' when 'merge_requests'
......
...@@ -78,17 +78,5 @@ RSpec.describe Ci::BatchResetMinutesWorker do ...@@ -78,17 +78,5 @@ RSpec.describe Ci::BatchResetMinutesWorker do
expect(last_namespace.reset.extra_shared_runners_minutes_limit).to eq 50 expect(last_namespace.reset.extra_shared_runners_minutes_limit).to eq 50
end end
end end
context 'when feature flag ci_parallel_minutes_reset is disabled' do
before do
stub_feature_flags(ci_parallel_minutes_reset: false)
end
it 'does not call Ci::Minutes::BatchResetService' do
expect(Ci::Minutes::BatchResetService).not_to receive(:new)
worker.perform(first_namespace.id, last_namespace.id)
end
end
end end
end end
...@@ -8,167 +8,36 @@ RSpec.describe ClearSharedRunnersMinutesWorker do ...@@ -8,167 +8,36 @@ RSpec.describe ClearSharedRunnersMinutesWorker do
describe '#perform' do describe '#perform' do
subject { worker.perform } subject { worker.perform }
context 'when ci_parallel_minutes_reset feature flag is disabled' do before do
let(:namespace) { create(:namespace) } [2, 3, 4, 5, 7, 8, 10, 14].each do |id|
create(:namespace, id: id)
before do
stub_feature_flags(ci_parallel_minutes_reset: false)
expect_next_instance_of(described_class) do |instance|
expect(instance).to receive(:try_obtain_lease).and_return(true)
end
end
context 'when project statistics are defined' do
let(:project) { create(:project, namespace: namespace) }
let(:statistics) { project.statistics }
before do
statistics.update!(shared_runners_seconds: 100)
end
it 'clears counters' do
subject
expect(statistics.reload.shared_runners_seconds).to be_zero
end
it 'resets timer' do
subject
expect(statistics.reload.shared_runners_seconds_last_reset).to be_like_time(Time.current)
end
context 'when there are namespaces that were not reset after the reset steps' do
let(:namespace_ids) { [namespace.id] }
before do
allow(Namespace).to receive(:each_batch).and_yield(Namespace.all)
allow(Namespace).to receive(:transaction).and_raise(ActiveRecord::ActiveRecordError)
end
it 'raises an exception' do
expect { worker.perform }.to raise_error(
Ci::Minutes::BatchResetService::BatchNotResetError,
'Some namespace shared runner minutes were not reset'
)
end
end
end
context 'when namespace statistics are defined' do
let!(:statistics) { create(:namespace_statistics, namespace: namespace, shared_runners_seconds: 100) }
it 'clears counters' do
subject
expect(statistics.reload.shared_runners_seconds).to be_zero
end
it 'resets timer' do
subject
expect(statistics.reload.shared_runners_seconds_last_reset).to be_like_time(Time.current)
end
end
context 'when namespace has extra shared runner minutes' do
let!(:namespace) do
create(:namespace, shared_runners_minutes_limit: 100, extra_shared_runners_minutes_limit: 10 )
end
let!(:statistics) do
create(:namespace_statistics, namespace: namespace, shared_runners_seconds: minutes_used * 60)
end
let(:minutes_used) { 0 }
context 'when consumption is below the default quota' do
let(:minutes_used) { 50 }
it 'does not modify the extra minutes quota' do
subject
expect(namespace.reload.extra_shared_runners_minutes_limit).to eq(10)
end
end
context 'when consumption is above the default quota' do
context 'when all extra minutes are used' do
let(:minutes_used) { 115 }
it 'sets extra minutes to 0' do
subject
expect(namespace.reload.extra_shared_runners_minutes_limit).to eq(0)
end
end
context 'when some extra minutes are used' do
let(:minutes_used) { 105 }
it 'discounts the extra minutes used' do
subject
expect(namespace.reload.extra_shared_runners_minutes_limit).to eq(5)
end
end
end
[:last_ci_minutes_notification_at, :last_ci_minutes_usage_notification_level].each do |attr|
context "when #{attr} is present" do
before do
namespace.update_attribute(attr, Time.current)
end
it 'nullifies the field' do
expect(namespace.send(attr)).to be_present
subject
expect(namespace.reload.send(attr)).not_to be_present
end
end
end
end end
end end
context 'when ci_parallel_minutes_reset feature flag is enabled' do context 'with batch size lower than count of namespaces' do
subject { worker.perform }
before do before do
stub_feature_flags(ci_parallel_minutes_reset: true) stub_const("#{described_class}::BATCH_SIZE", 3)
[2, 3, 4, 5, 7, 8, 10, 14].each do |id|
create(:namespace, id: id)
end
end end
context 'with batch size lower than count of namespaces' do it 'runs a worker per batch', :aggregate_failures do
before do # Spreads evenly accross 8 hours (28,800 seconds)
stub_const("#{described_class}::BATCH_SIZE", 3) expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(0.seconds, 2, 4)
end expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(7200.seconds, 5, 7)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(14400.seconds, 8, 10)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(21600.seconds, 11, 13)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(28800.seconds, 14, 16)
it 'runs a worker per batch', :aggregate_failures do subject
# Spreads evenly accross 8 hours (28,800 seconds)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(0.seconds, 2, 4)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(7200.seconds, 5, 7)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(14400.seconds, 8, 10)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(21600.seconds, 11, 13)
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(28800.seconds, 14, 16)
subject
end
end end
end
context 'with batch size higher than count of namespaces' do context 'with batch size higher than count of namespaces' do
# Uses default BATCH_SIZE # Uses default BATCH_SIZE
it 'runs the worker in a single batch', :aggregate_failures do it 'runs the worker in a single batch', :aggregate_failures do
# Runs a single batch, immediately # Runs a single batch, immediately
expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(0.seconds, 2, 100001) expect(Ci::BatchResetMinutesWorker).to receive(:perform_in).with(0.seconds, 2, 100001)
subject subject
end
end end
end end
end end
......
...@@ -39,11 +39,11 @@ module API ...@@ -39,11 +39,11 @@ module API
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {}) def self.preload_relation(projects_relation, options = {})
# Preloading topics, should be done with using only `:topics`, # Preloading topics, should be done with using only `:topics`,
# as `:topics` are defined as: `has_many :topics, through: :taggings` # as `:topics` are defined as: `has_many :topics, through: :project_topics`
# N+1 is solved then by using `subject.topics.map(&:name)` # N+1 is solved then by using `subject.topics.map(&:name)`
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
projects_relation.preload(:project_feature, :route) projects_relation.preload(:project_feature, :route)
.preload(:import_state, :topics, :topics_acts_as_taggable) .preload(:import_state, :topics)
.preload(:auto_devops) .preload(:auto_devops)
.preload(namespace: [:route, :owner]) .preload(namespace: [:route, :owner])
end end
......
...@@ -132,7 +132,7 @@ module API ...@@ -132,7 +132,7 @@ module API
def self.preload_relation(projects_relation, options = {}) def self.preload_relation(projects_relation, options = {})
# Preloading topics, should be done with using only `:topics`, # Preloading topics, should be done with using only `:topics`,
# as `:topics` are defined as: `has_many :topics, through: :taggings` # as `:topics` are defined as: `has_many :topics, through: :project_topics`
# N+1 is solved then by using `subject.topics.map(&:name)` # N+1 is solved then by using `subject.topics.map(&:name)`
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
super(projects_relation).preload(group: :namespace_settings) super(projects_relation).preload(group: :namespace_settings)
...@@ -144,7 +144,7 @@ module API ...@@ -144,7 +144,7 @@ module API
.preload(project_group_links: { group: :route }, .preload(project_group_links: { group: :route },
fork_network: :root_project, fork_network: :root_project,
fork_network_member: :forked_from_project, fork_network_member: :forked_from_project,
forked_from_project: [:route, :topics, :topics_acts_as_taggable, :group, :project_feature, namespace: [:route, :owner]]) forked_from_project: [:route, :topics, :group, :project_feature, namespace: [:route, :owner]])
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -2396,9 +2396,15 @@ msgstr "" ...@@ -2396,9 +2396,15 @@ msgstr ""
msgid "AdminSettings|Disable public access to Pages sites" msgid "AdminSettings|Disable public access to Pages sites"
msgstr "" msgstr ""
msgid "AdminSettings|Display a banner on merge requests in projects with no pipelines to initiate steps to add a .gitlab-ci.yml file."
msgstr ""
msgid "AdminSettings|Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled. %{link_start}Learn more.%{link_end}" msgid "AdminSettings|Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled. %{link_start}Learn more.%{link_end}"
msgstr "" msgstr ""
msgid "AdminSettings|Enable pipeline suggestion banner"
msgstr ""
msgid "AdminSettings|Enable shared runners for new projects" msgid "AdminSettings|Enable shared runners for new projects"
msgstr "" msgstr ""
......
...@@ -314,12 +314,14 @@ RSpec.describe 'Admin updates settings' do ...@@ -314,12 +314,14 @@ RSpec.describe 'Admin updates settings' do
check 'Default to Auto DevOps pipeline for all projects' check 'Default to Auto DevOps pipeline for all projects'
fill_in 'application_setting_auto_devops_domain', with: 'domain.com' fill_in 'application_setting_auto_devops_domain', with: 'domain.com'
uncheck 'Keep the latest artifacts for all jobs in the latest successful pipelines' uncheck 'Keep the latest artifacts for all jobs in the latest successful pipelines'
uncheck 'Enable pipeline suggestion banner'
click_button 'Save changes' click_button 'Save changes'
end end
expect(current_settings.auto_devops_enabled?).to be true expect(current_settings.auto_devops_enabled?).to be true
expect(current_settings.auto_devops_domain).to eq('domain.com') expect(current_settings.auto_devops_domain).to eq('domain.com')
expect(current_settings.keep_latest_artifact).to be false expect(current_settings.keep_latest_artifact).to be false
expect(current_settings.suggest_pipeline_enabled).to be false
expect(page).to have_content "Application settings saved successfully" expect(page).to have_content "Application settings saved successfully"
end end
......
...@@ -355,10 +355,7 @@ container_repositories: ...@@ -355,10 +355,7 @@ container_repositories:
- name - name
project: project:
- external_status_checks - external_status_checks
- taggings
- base_tags - base_tags
- topic_taggings
- topics_acts_as_taggable
- project_topics - project_topics
- topics - topics
- chat_services - chat_services
......
...@@ -7240,35 +7240,6 @@ RSpec.describe Project, factory_default: :keep do ...@@ -7240,35 +7240,6 @@ RSpec.describe Project, factory_default: :keep do
expect(project.reload.topics.map(&:name)).to eq(%w[topic1 topic2 topic3]) expect(project.reload.topics.map(&:name)).to eq(%w[topic1 topic2 topic3])
end end
end end
context 'during ExtractProjectTopicsIntoSeparateTable migration' do
before do
topic_a = ActsAsTaggableOn::Tag.find_or_create_by!(name: 'topicA')
topic_b = ActsAsTaggableOn::Tag.find_or_create_by!(name: 'topicB')
project.reload.topics_acts_as_taggable = [topic_a, topic_b]
project.save!
project.reload
end
it 'topic_list returns correct string array' do
expect(project.topic_list).to eq(%w[topicA topicB topic1 topic2 topic3])
end
it 'topics returns correct topic records' do
expect(project.topics.map(&:class)).to eq([ActsAsTaggableOn::Tag, ActsAsTaggableOn::Tag, Projects::Topic, Projects::Topic, Projects::Topic])
expect(project.topics.map(&:name)).to eq(%w[topicA topicB topic1 topic2 topic3])
end
it 'topic_list= sets new topics and removes old topics' do
project.topic_list = 'new-topic1, new-topic2'
project.save!
project.reload
expect(project.topics.map(&:class)).to eq([Projects::Topic, Projects::Topic])
expect(project.topics.map(&:name)).to eq(%w[new-topic1 new-topic2])
end
end
end end
shared_examples 'all_runners' do shared_examples 'all_runners' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment