diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-25 21:10:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-25 21:10:42 +0300 |
commit | a880341a7b3a164ba381620852cc3ea0777f67ad (patch) | |
tree | de7f7a09aee296eef7924cad7eb7ec2e9ea29ce0 | |
parent | a8c1bc6f757ecacbc3481e52a3f4cefb6c6db5fd (diff) |
Add latest changes from gitlab-org/gitlab@master
102 files changed, 1222 insertions, 1210 deletions
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index df99014e425..4fb55ecaf6b 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -1649,19 +1649,14 @@ Gitlab/NamespacedClass: - 'app/models/project_repository_storage_move.rb' - 'app/models/project_services/alerts_service.rb' - 'app/models/project_services/alerts_service_data.rb' - - 'app/models/project_services/buildkite_service.rb' - 'app/models/project_services/chat_notification_service.rb' - - 'app/models/project_services/ci_service.rb' - 'app/models/project_services/discord_service.rb' - - 'app/models/project_services/drone_ci_service.rb' - 'app/models/project_services/hangouts_chat_service.rb' - 'app/models/project_services/issue_tracker_data.rb' - - 'app/models/project_services/jenkins_service.rb' - 'app/models/project_services/jira_tracker_data.rb' - 'app/models/project_services/mattermost_service.rb' - 'app/models/project_services/mattermost_slash_commands_service.rb' - 'app/models/project_services/microsoft_teams_service.rb' - - 'app/models/project_services/mock_ci_service.rb' - 'app/models/project_services/mock_monitoring_service.rb' - 'app/models/project_services/monitoring_service.rb' - 'app/models/project_services/open_project_tracker_data.rb' @@ -1670,7 +1665,6 @@ Gitlab/NamespacedClass: - 'app/models/project_services/slack_service.rb' - 'app/models/project_services/slack_slash_commands_service.rb' - 'app/models/project_services/slash_commands_service.rb' - - 'app/models/project_services/teamcity_service.rb' - 'app/models/project_services/unify_circuit_service.rb' - 'app/models/project_services/webex_teams_service.rb' - 'app/models/project_setting.rb' diff --git a/app/assets/javascripts/branches/components/sort_dropdown.vue b/app/assets/javascripts/branches/components/sort_dropdown.vue index ddb4c5c0015..5f782b5e652 100644 --- a/app/assets/javascripts/branches/components/sort_dropdown.vue +++ b/app/assets/javascripts/branches/components/sort_dropdown.vue @@ -62,17 +62,18 @@ export default { }; </script> <template> - <div class="gl-display-flex gl-pr-4"> + <div class="gl-display-flex"> <gl-search-box-by-click v-model="searchTerm" :placeholder="$options.i18n.searchPlaceholder" - class="gl-pr-4" + class="gl-mr-3" data-testid="branch-search" @submit="visitUrlFromOption(selectedKey)" /> <gl-dropdown v-if="shouldShowDropdown" :text="selectedSortMethodName" + class="gl-mr-3" data-testid="branches-dropdown" > <gl-dropdown-item diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index a3e8b2c245c..9fe9f9a845c 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -135,7 +135,7 @@ ul.content-list { float: right; > .control-text { - margin-right: $gl-padding-top; + margin-right: $grid-size; line-height: $list-text-height; &:last-child { @@ -148,8 +148,6 @@ ul.content-list { > .dropdown.inline { margin-right: $grid-size; display: inline-block; - margin-top: 3px; - margin-bottom: 4px; &.btn-ldap-override { @include media-breakpoint-up(sm) { diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index fc4f9aa3409..b7f6691ef4b 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -177,7 +177,7 @@ module WikiActions redirect_to wiki_path(wiki), status: :found else - @error = response + @error = response.message render 'shared/wikis/edit' end end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 5006aa75ce5..3704f01e5dc 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -152,7 +152,7 @@ class Projects::BranchesController < Projects::ApplicationController ref_escaped = strip_tags(sanitize(params[:ref])) Addressable::URI.unescape(ref_escaped) else - @project.default_branch || 'master' + @project.default_branch_or_main end end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 272747a124e..582075efc4e 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -14,7 +14,8 @@ # starred: boolean # sort: string # visibility_level: int -# tags: string[] +# tag: string[] - deprecated, use 'topic' instead +# topic: string[] # personal: boolean # search: string # search_namespaces: boolean @@ -37,6 +38,8 @@ class ProjectsFinder < UnionFinder @params = params @current_user = current_user @project_ids_relation = project_ids_relation + + @params[:topic] ||= @params.delete(:tag) if @params[:tag].present? end def execute @@ -76,7 +79,7 @@ class ProjectsFinder < UnionFinder collection = by_starred(collection) collection = by_trending(collection) collection = by_visibility_level(collection) - collection = by_tags(collection) + collection = by_topics(collection) collection = by_search(collection) collection = by_archived(collection) collection = by_custom_attributes(collection) @@ -176,8 +179,8 @@ class ProjectsFinder < UnionFinder end # rubocop: enable CodeReuse/ActiveRecord - def by_tags(items) - params[:tag].present? ? items.tagged_with(params[:tag]) : items + def by_topics(items) + params[:topic].present? ? items.tagged_with(params[:topic]) : items end def by_search(items) diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 4094bdb26dc..8b3b298f5a2 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -10,6 +10,7 @@ module Ci include Presentable include ChronicDurationAttribute include Gitlab::Utils::StrongMemoize + include IgnorableColumns self.table_name = 'ci_builds_metadata' @@ -37,6 +38,8 @@ module Ci job_timeout_source: 4 } + ignore_column :build_id_convert_to_bigint, remove_with: '14.2', remove_after: '2021-08-22' + def update_timeout_state timeout = timeout_with_highest_precedence diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 5d6c8542a74..b157b2f4a6c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -922,6 +922,12 @@ module Ci Ci::Build.latest.where(pipeline: self_and_descendants) end + def environments_in_self_and_descendants + environment_ids = self_and_descendants.joins(:deployments).select(:'deployments.environment_id') + + Environment.where(id: environment_ids) + end + # Without using `unscoped`, caller scope is also included into the query. # Using `unscoped` here will be redundant after Rails 6.1 def self_and_descendants diff --git a/app/models/concerns/service_push_data_validations.rb b/app/models/concerns/service_push_data_validations.rb index defc5794142..451804a2c56 100644 --- a/app/models/concerns/service_push_data_validations.rb +++ b/app/models/concerns/service_push_data_validations.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# This concern is used by registerd services such as TeamCityService and -# DroneCiService and add methods to perform validations on the received +# This concern is used by registered integrations such as Integrations::TeamCity and +# Integrations::DroneCi and adds methods to perform validations on the received # data. module ServicePushDataValidations diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb index 55ea4e2fe18..07c0983f239 100644 --- a/app/models/environment_status.rb +++ b/app/models/environment_status.rb @@ -100,7 +100,7 @@ class EnvironmentStatus def self.build_environments_status(mr, user, pipeline) return [] unless pipeline - pipeline.environments.includes(:project).available.map do |environment| + pipeline.environments_in_self_and_descendants.includes(:project).available.map do |environment| next unless Ability.allowed?(user, :read_environment, environment) EnvironmentStatus.new(pipeline.project, environment, mr, pipeline.sha) diff --git a/app/models/integrations/bamboo.rb b/app/models/integrations/bamboo.rb index 82111c7322e..dbd7aedf4fe 100644 --- a/app/models/integrations/bamboo.rb +++ b/app/models/integrations/bamboo.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Integrations - class Bamboo < CiService + class Bamboo < BaseCi include ActionView::Helpers::UrlHelper include ReactiveService diff --git a/app/models/integrations/base_ci.rb b/app/models/integrations/base_ci.rb new file mode 100644 index 00000000000..b2e269b1b50 --- /dev/null +++ b/app/models/integrations/base_ci.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Base class for CI services +# List methods you need to implement to get your CI service +# working with GitLab merge requests +module Integrations + class BaseCi < Integration + default_value_for :category, 'ci' + + def valid_token?(token) + self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token) + end + + def self.supported_events + %w(push) + end + + # Return complete url to build page + # + # Ex. + # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c + # + def build_page(sha, ref) + # implement inside child + end + + # Return string with build status or :error symbol + # + # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' + # + # + # Ex. + # @service.commit_status('13be4ac', 'master') + # # => 'success' + # + # @service.commit_status('2abe4ac', 'dev') + # # => 'running' + # + # + def commit_status(sha, ref) + # implement inside child + end + end +end diff --git a/app/models/integrations/buildkite.rb b/app/models/integrations/buildkite.rb new file mode 100644 index 00000000000..906a5d02f9c --- /dev/null +++ b/app/models/integrations/buildkite.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require "addressable/uri" + +module Integrations + class Buildkite < BaseCi + include ReactiveService + + ENDPOINT = "https://buildkite.com" + + prop_accessor :project_url, :token + + validates :project_url, presence: true, public_url: true, if: :activated? + validates :token, presence: true, if: :activated? + + after_save :compose_service_hook, if: :activated? + + def self.supported_events + %w(push merge_request tag_push) + end + + # This is a stub method to work with deprecated API response + # TODO: remove enable_ssl_verification after 14.0 + # https://gitlab.com/gitlab-org/gitlab/-/issues/222808 + def enable_ssl_verification + true + end + + # Since SSL verification will always be enabled for Buildkite, + # we no longer needs to store the boolean. + # This is a stub method to work with deprecated API param. + # TODO: remove enable_ssl_verification after 14.0 + # https://gitlab.com/gitlab-org/gitlab/-/issues/222808 + def enable_ssl_verification=(_value) + self.properties.delete('enable_ssl_verification') # Remove unused key + end + + def webhook_url + "#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}" + end + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = webhook_url + hook.enable_ssl_verification = true + hook.save + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + service_hook.execute(data) + end + + def commit_status(sha, ref) + with_reactive_cache(sha, ref) {|cached| cached[:commit_status] } + end + + def commit_status_path(sha) + "#{buildkite_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}" + end + + def build_page(sha, ref) + "#{project_url}/builds?commit=#{sha}" + end + + def title + 'Buildkite' + end + + def description + 'Run CI/CD pipelines with Buildkite.' + end + + def self.to_param + 'buildkite' + end + + def fields + [ + { type: 'text', + name: 'token', + title: 'Integration Token', + help: 'This token will be provided when you create a Buildkite pipeline with a GitLab repository', + required: true }, + + { type: 'text', + name: 'project_url', + title: 'Pipeline URL', + placeholder: "#{ENDPOINT}/acme-inc/test-pipeline", + required: true } + ] + end + + def calculate_reactive_cache(sha, ref) + response = Gitlab::HTTP.try_get(commit_status_path(sha), request_options) + + status = + if response&.code == 200 && response['status'] + response['status'] + else + :error + end + + { commit_status: status } + end + + private + + def webhook_token + token_parts.first + end + + def status_token + token_parts.second + end + + def token_parts + if token.present? + token.split(':') + else + [] + end + end + + def buildkite_endpoint(subdomain = nil) + if subdomain.present? + uri = Addressable::URI.parse(ENDPOINT) + new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}" + + if uri.port.present? + "#{new_endpoint}:#{uri.port}" + else + new_endpoint + end + else + ENDPOINT + end + end + + def request_options + { verify: false, extra_log_info: { project_id: project_id } } + end + end +end diff --git a/app/models/integrations/drone_ci.rb b/app/models/integrations/drone_ci.rb new file mode 100644 index 00000000000..096f7093b8c --- /dev/null +++ b/app/models/integrations/drone_ci.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module Integrations + class DroneCi < BaseCi + include ReactiveService + include ServicePushDataValidations + + prop_accessor :drone_url, :token + boolean_accessor :enable_ssl_verification + + validates :drone_url, presence: true, public_url: true, if: :activated? + validates :token, presence: true, if: :activated? + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + # If using a service template, project may not be available + hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join if project + hook.enable_ssl_verification = !!enable_ssl_verification + hook.save + end + + def execute(data) + case data[:object_kind] + when 'push' + service_hook.execute(data) if push_valid?(data) + when 'merge_request' + service_hook.execute(data) if merge_request_valid?(data) + when 'tag_push' + service_hook.execute(data) if tag_push_valid?(data) + end + end + + def allow_target_ci? + true + end + + def self.supported_events + %w(push merge_request tag_push) + end + + def commit_status_path(sha, ref) + Gitlab::Utils.append_path( + drone_url, + "gitlab/#{project.full_path}/commits/#{sha}?branch=#{Addressable::URI.encode_component(ref.to_s)}&access_token=#{token}") + end + + def commit_status(sha, ref) + with_reactive_cache(sha, ref) { |cached| cached[:commit_status] } + end + + def calculate_reactive_cache(sha, ref) + response = Gitlab::HTTP.try_get(commit_status_path(sha, ref), + verify: enable_ssl_verification, + extra_log_info: { project_id: project_id }) + + status = + if response && response.code == 200 && response['status'] + case response['status'] + when 'killed' + :canceled + when 'failure', 'error' + # Because drone return error if some test env failed + :failed + else + response["status"] + end + else + :error + end + + { commit_status: status } + end + + def build_page(sha, ref) + Gitlab::Utils.append_path( + drone_url, + "gitlab/#{project.full_path}/redirect/commits/#{sha}?branch=#{Addressable::URI.encode_component(ref.to_s)}") + end + + def title + 'Drone' + end + + def description + s_('ProjectService|Run CI/CD pipelines with Drone.') + end + + def self.to_param + 'drone_ci' + end + + def help + s_('ProjectService|Run CI/CD pipelines with Drone.') + end + + def fields + [ + { type: 'text', name: 'token', help: s_('ProjectService|Token for the Drone project.'), required: true }, + { type: 'text', name: 'drone_url', title: s_('ProjectService|Drone server URL'), placeholder: 'http://drone.example.com', required: true }, + { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } + ] + end + end +end diff --git a/app/models/integrations/jenkins.rb b/app/models/integrations/jenkins.rb new file mode 100644 index 00000000000..815e86bcaa1 --- /dev/null +++ b/app/models/integrations/jenkins.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module Integrations + class Jenkins < BaseCi + include ActionView::Helpers::UrlHelper + + prop_accessor :jenkins_url, :project_name, :username, :password + + before_update :reset_password + + validates :jenkins_url, presence: true, addressable_url: true, if: :activated? + validates :project_name, presence: true, if: :activated? + validates :username, presence: true, if: ->(service) { service.activated? && service.password_touched? && service.password.present? } + + default_value_for :push_events, true + default_value_for :merge_requests_events, false + default_value_for :tag_push_events, false + + after_save :compose_service_hook, if: :activated? + + def reset_password + # don't reset the password if a new one is provided + if (jenkins_url_changed? || username.blank?) && !password_touched? + self.password = nil + end + end + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = hook_url + hook.save + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + service_hook.execute(data, "#{data[:object_kind]}_hook") + end + + def test(data) + begin + result = execute(data) + return { success: false, result: result[:message] } if result[:http_status] != 200 + rescue StandardError => error + return { success: false, result: error } + end + + { success: true, result: result[:message] } + end + + def hook_url + url = URI.parse(jenkins_url) + url.path = File.join(url.path || '/', "project/#{project_name}") + url.user = ERB::Util.url_encode(username) unless username.blank? + url.password = ERB::Util.url_encode(password) unless password.blank? + url.to_s + end + + def self.supported_events + %w(push merge_request tag_push) + end + + def title + 'Jenkins' + end + + def description + s_('Run CI/CD pipelines with Jenkins.') + end + + def help + docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('integration/jenkins'), target: '_blank', rel: 'noopener noreferrer' + s_('Run CI/CD pipelines with Jenkins when you push to a repository, or when a merge request is created, updated, or merged. %{docs_link}').html_safe % { docs_link: docs_link.html_safe } + end + + def self.to_param + 'jenkins' + end + + def fields + [ + { + type: 'text', + name: 'jenkins_url', + title: s_('ProjectService|Jenkins server URL'), + required: true, + placeholder: 'http://jenkins.example.com', + help: s_('The URL of the Jenkins server.') + }, + { + type: 'text', + name: 'project_name', + required: true, + placeholder: 'my_project_name', + help: s_('The name of the Jenkins project. Copy the name from the end of the URL to the project.') + }, + { + type: 'text', + name: 'username', + required: true, + help: s_('The username for the Jenkins server.') + }, + { + type: 'password', + name: 'password', + help: s_('The password for the Jenkins server.'), + non_empty_password_title: s_('ProjectService|Enter new password.'), + non_empty_password_help: s_('ProjectService|Leave blank to use your current password.') + } + ] + end + end +end diff --git a/app/models/integrations/mock_ci.rb b/app/models/integrations/mock_ci.rb new file mode 100644 index 00000000000..d31f6381767 --- /dev/null +++ b/app/models/integrations/mock_ci.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service +module Integrations + class MockCi < BaseCi + ALLOWED_STATES = %w[failed canceled running pending success success-with-warnings skipped not_found].freeze + + prop_accessor :mock_service_url + validates :mock_service_url, presence: true, public_url: true, if: :activated? + + def title + 'MockCI' + end + + def description + 'Mock an external CI' + end + + def self.to_param + 'mock_ci' + end + + def fields + [ + { + type: 'text', + name: 'mock_service_url', + title: s_('ProjectService|Mock service URL'), + placeholder: 'http://localhost:4004', + required: true + } + ] + end + + # Return complete url to build page + # + # Ex. + # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c + # + def build_page(sha, ref) + Gitlab::Utils.append_path( + mock_service_url, + "#{project.namespace.path}/#{project.path}/status/#{sha}") + end + + # Return string with build status or :error symbol + # + # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' + # + # Ex. + # @service.commit_status('13be4ac', 'master') + # # => 'success' + # + # @service.commit_status('2abe4ac', 'dev') + # # => 'running' + # + def commit_status(sha, ref) + response = Gitlab::HTTP.get(commit_status_path(sha), verify: false) + read_commit_status(response) + rescue Errno::ECONNREFUSED + :error + end + + def commit_status_path(sha) + Gitlab::Utils.append_path( + mock_service_url, + "#{project.namespace.path}/#{project.path}/status/#{sha}.json") + end + + def read_commit_status(response) + return :error unless response.code == 200 || response.code == 404 + + status = if response.code == 404 + 'pending' + else + response['status'] + end + + if status.present? && ALLOWED_STATES.include?(status) + status + else + :error + end + end + + def can_test? + false + end + end +end diff --git a/app/models/integrations/teamcity.rb b/app/models/integrations/teamcity.rb new file mode 100644 index 00000000000..8284d5963ae --- /dev/null +++ b/app/models/integrations/teamcity.rb @@ -0,0 +1,191 @@ +# frozen_string_literal: true + +module Integrations + class Teamcity < BaseCi + include ReactiveService + include ServicePushDataValidations + + prop_accessor :teamcity_url, :build_type, :username, :password + + validates :teamcity_url, presence: true, public_url: true, if: :activated? + validates :build_type, presence: true, if: :activated? + validates :username, + presence: true, + if: ->(service) { service.activated? && service.password } + validates :password, + presence: true, + if: ->(service) { service.activated? && service.username } + + attr_accessor :response + + after_save :compose_service_hook, if: :activated? + before_update :reset_password + + class << self + def to_param + 'teamcity' + end + + def supported_events + %w(push merge_request) + end + + def event_description(event) + case event + when 'push', 'push_events' + 'TeamCity CI will be triggered after every push to the repository except branch delete' + when 'merge_request', 'merge_request_events' + 'TeamCity CI will be triggered after a merge request has been created or updated' + end + end + end + + def compose_service_hook + hook = service_hook || build_service_hook + hook.save + end + + def reset_password + if teamcity_url_changed? && !password_touched? + self.password = nil + end + end + + def title + 'JetBrains TeamCity' + end + + def description + s_('ProjectService|Run CI/CD pipelines with JetBrains TeamCity.') + end + + def help + s_('To run CI/CD pipelines with JetBrains TeamCity, input the GitLab project details in the TeamCity project Version Control Settings.') + end + + def fields + [ + { + type: 'text', + name: 'teamcity_url', + title: s_('ProjectService|TeamCity server URL'), + placeholder: 'https://teamcity.example.com', + required: true + }, + { + type: 'text', + name: 'build_type', + help: s_('ProjectService|The build configuration ID of the TeamCity project.'), + required: true + }, + { + type: 'text', + name: 'username', + help: s_('ProjectService|Must have permission to trigger a manual build in TeamCity.') + }, + { + type: 'password', + name: 'password', + non_empty_password_title: s_('ProjectService|Enter new password'), + non_empty_password_help: s_('ProjectService|Leave blank to use your current password') + } + ] + end + + def build_page(sha, ref) + with_reactive_cache(sha, ref) {|cached| cached[:build_page] } + end + + def commit_status(sha, ref) + with_reactive_cache(sha, ref) {|cached| cached[:commit_status] } + end + + def calculate_reactive_cache(sha, ref) + response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,revision:#{sha}") + + if response + { build_page: read_build_page(response), commit_status: read_commit_status(response) } + else + { build_page: teamcity_url, commit_status: :error } + end + end + + def execute(data) + case data[:object_kind] + when 'push' + execute_push(data) + when 'merge_request' + execute_merge_request(data) + end + end + + private + + def execute_push(data) + branch = Gitlab::Git.ref_name(data[:ref]) + post_to_build_queue(data, branch) if push_valid?(data) + end + + def execute_merge_request(data) + branch = data[:object_attributes][:source_branch] + post_to_build_queue(data, branch) if merge_request_valid?(data) + end + + def read_build_page(response) + if response.code != 200 + # If actual build link can't be determined, + # send user to build summary page. + build_url("viewLog.html?buildTypeId=#{build_type}") + else + # If actual build link is available, go to build result page. + built_id = response['build']['id'] + build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}") + end + end + + def read_commit_status(response) + return :error unless response.code == 200 || response.code == 404 + + status = if response.code == 404 + 'Pending' + else + response['build']['status'] + end + + return :error unless status.present? + + if status.include?('SUCCESS') + 'success' + elsif status.include?('FAILURE') + 'failed' + elsif status.include?('Pending') + 'pending' + else + :error + end + end + + def build_url(path) + Gitlab::Utils.append_path(teamcity_url, path) + end + + def get_path(path) + Gitlab::HTTP.try_get(build_url(path), verify: false, basic_auth: basic_auth, extra_log_info: { project_id: project_id }) + end + + def post_to_build_queue(data, branch) + Gitlab::HTTP.post( + build_url('httpAuth/app/rest/buildQueue'), + body: "<build branchName=#{branch.encode(xml: :attr)}>"\ + "<buildType id=#{build_type.encode(xml: :attr)}/>"\ + '</build>', + headers: { 'Content-type' => 'application/xml' }, + basic_auth: basic_auth + ) + end + + def basic_auth + { username: username, password: password } + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 39415769e50..b2324b18e17 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -129,41 +129,15 @@ class Project < ApplicationRecord after_create :check_repository_absence! acts_as_ordered_taggable_on :topics - # The 'tag_list' alias and the 'has_many' associations are required during the 'tags -> topics' migration - # TODO: eliminate 'tag_list', 'topic_taggings' and 'tags' in the further process of the migration - # https://gitlab.com/gitlab-org/gitlab/-/issues/331081 + # The 'tag_list' alias and the 'tags' association are required during the 'tags -> topics' migration + # TODO: eliminate 'tag_list' and 'tags' in the further process of the migration + # https://gitlab.com/gitlab-org/gitlab/-/issues/328226 alias_attribute :tag_list, :topic_list - has_many :topic_taggings, -> { includes(:tag).order("#{ActsAsTaggableOn::Tagging.table_name}.id") }, - as: :taggable, - class_name: 'ActsAsTaggableOn::Tagging', - after_add: :dirtify_tag_list, - after_remove: :dirtify_tag_list - has_many :topics, -> { order("#{ActsAsTaggableOn::Tagging.table_name}.id") }, - class_name: 'ActsAsTaggableOn::Tag', - through: :topic_taggings, - source: :tag has_many :tags, -> { order("#{ActsAsTaggableOn::Tagging.table_name}.id") }, class_name: 'ActsAsTaggableOn::Tag', through: :topic_taggings, source: :tag - # Overwriting 'topic_list' and 'topic_list=' is necessary to ensure functionality during the background migration [1]. - # [1] https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61237 - # TODO: remove 'topic_list' and 'topic_list=' once the background migration is complete - # https://gitlab.com/gitlab-org/gitlab/-/issues/331081 - def topic_list - # Return both old topics (context 'tags') and new topics (context 'topics') - tag_list_on('tags') + tag_list_on('topics') - end - - def topic_list=(new_tags) - # Old topics with context 'tags' are added as new topics with context 'topics' - super(new_tags) - - # Remove old topics with context 'tags' - set_tag_list_on('tags', '') - end - attr_accessor :old_path_with_namespace attr_accessor :template_name attr_writer :pipeline_status @@ -187,33 +161,33 @@ class Project < ApplicationRecord has_one :assembla_service, class_name: 'Integrations::Assembla' has_one :bamboo_service, class_name: 'Integrations::Bamboo' has_one :bugzilla_service, class_name: 'Integrations::Bugzilla' + has_one :buildkite_service, class_name: 'Integrations::Buildkite' has_one :campfire_service, class_name: 'Integrations::Campfire' has_one :confluence_service, class_name: 'Integrations::Confluence' has_one :custom_issue_tracker_service, class_name: 'Integrations::CustomIssueTracker' has_one :datadog_service, class_name: 'Integrations::Datadog' + has_one :drone_ci_service, class_name: 'Integrations::DroneCi' has_one :emails_on_push_service, class_name: 'Integrations::EmailsOnPush' has_one :ewm_service, class_name: 'Integrations::Ewm' has_one :external_wiki_service, class_name: 'Integrations::ExternalWiki' has_one :flowdock_service, class_name: 'Integrations::Flowdock' has_one :irker_service, class_name: 'Integrations::Irker' + has_one :jenkins_service, class_name: 'Integrations::Jenkins' has_one :jira_service, class_name: 'Integrations::Jira' + has_one :mock_ci_service, class_name: 'Integrations::MockCi' has_one :packagist_service, class_name: 'Integrations::Packagist' has_one :pipelines_email_service, class_name: 'Integrations::PipelinesEmail' has_one :pivotaltracker_service, class_name: 'Integrations::Pivotaltracker' has_one :redmine_service, class_name: 'Integrations::Redmine' + has_one :teamcity_service, class_name: 'Integrations::Teamcity' has_one :youtrack_service, class_name: 'Integrations::Youtrack' has_one :discord_service - has_one :drone_ci_service has_one :mattermost_slash_commands_service has_one :mattermost_service has_one :slack_slash_commands_service has_one :slack_service - has_one :buildkite_service - has_one :teamcity_service has_one :pushover_service - has_one :jenkins_service has_one :prometheus_service, inverse_of: :project - has_one :mock_ci_service has_one :mock_monitoring_service has_one :microsoft_teams_service has_one :hangouts_chat_service diff --git a/app/models/project_repository_storage_move.rb b/app/models/project_repository_storage_move.rb deleted file mode 100644 index e54489ddb88..00000000000 --- a/app/models/project_repository_storage_move.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -# This is a compatibility class to avoid calling a non-existent -# class from sidekiq during deployment. -# -# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853. -# we cannot remove this class entirely because there can be jobs -# referencing it. -# -# We can get rid of this class in 14.0 -# https://gitlab.com/gitlab-org/gitlab/-/issues/322393 -class ProjectRepositoryStorageMove < Projects::RepositoryStorageMove -end diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb deleted file mode 100644 index f2ea5066e37..00000000000 --- a/app/models/project_services/buildkite_service.rb +++ /dev/null @@ -1,143 +0,0 @@ -# frozen_string_literal: true - -require "addressable/uri" - -class BuildkiteService < CiService - include ReactiveService - - ENDPOINT = "https://buildkite.com" - - prop_accessor :project_url, :token - - validates :project_url, presence: true, public_url: true, if: :activated? - validates :token, presence: true, if: :activated? - - after_save :compose_service_hook, if: :activated? - - def self.supported_events - %w(push merge_request tag_push) - end - - # This is a stub method to work with deprecated API response - # TODO: remove enable_ssl_verification after 14.0 - # https://gitlab.com/gitlab-org/gitlab/-/issues/222808 - def enable_ssl_verification - true - end - - # Since SSL verification will always be enabled for Buildkite, - # we no longer needs to store the boolean. - # This is a stub method to work with deprecated API param. - # TODO: remove enable_ssl_verification after 14.0 - # https://gitlab.com/gitlab-org/gitlab/-/issues/222808 - def enable_ssl_verification=(_value) - self.properties.delete('enable_ssl_verification') # Remove unused key - end - - def webhook_url - "#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}" - end - - def compose_service_hook - hook = service_hook || build_service_hook - hook.url = webhook_url - hook.enable_ssl_verification = true - hook.save - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - service_hook.execute(data) - end - - def commit_status(sha, ref) - with_reactive_cache(sha, ref) {|cached| cached[:commit_status] } - end - - def commit_status_path(sha) - "#{buildkite_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}" - end - - def build_page(sha, ref) - "#{project_url}/builds?commit=#{sha}" - end - - def title - 'Buildkite' - end - - def description - 'Run CI/CD pipelines with Buildkite.' - end - - def self.to_param - 'buildkite' - end - - def fields - [ - { type: 'text', - name: 'token', - title: 'Integration Token', - help: 'This token will be provided when you create a Buildkite pipeline with a GitLab repository', - required: true }, - - { type: 'text', - name: 'project_url', - title: 'Pipeline URL', - placeholder: "#{ENDPOINT}/acme-inc/test-pipeline", - required: true } - ] - end - - def calculate_reactive_cache(sha, ref) - response = Gitlab::HTTP.try_get(commit_status_path(sha), request_options) - - status = - if response&.code == 200 && response['status'] - response['status'] - else - :error - end - - { commit_status: status } - end - - private - - def webhook_token - token_parts.first - end - - def status_token - token_parts.second - end - - def token_parts - if token.present? - token.split(':') - else - [] - end - end - - def buildkite_endpoint(subdomain = nil) - if subdomain.present? - uri = Addressable::URI.parse(ENDPOINT) - new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}" - - if uri.port.present? - "#{new_endpoint}:#{uri.port}" - else - new_endpoint - end - else - ENDPOINT - end - end - - def request_options - { verify: false, extra_log_info: { project_id: project_id } } - end -end diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb deleted file mode 100644 index 0733da761d5..00000000000 --- a/app/models/project_services/ci_service.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -# Base class for CI services -# List methods you need to implement to get your CI service -# working with GitLab merge requests -class CiService < Integration - default_value_for :category, 'ci' - - def valid_token?(token) - self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token) - end - - def self.supported_events - %w(push) - end - - # Return complete url to build page - # - # Ex. - # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c - # - def build_page(sha, ref) - # implement inside child - end - - # Return string with build status or :error symbol - # - # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' - # - # - # Ex. - # @service.commit_status('13be4ac', 'master') - # # => 'success' - # - # @service.commit_status('2abe4ac', 'dev') - # # => 'running' - # - # - def commit_status(sha, ref) - # implement inside child - end -end diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb deleted file mode 100644 index ab1ba768a8f..00000000000 --- a/app/models/project_services/drone_ci_service.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -class DroneCiService < CiService - include ReactiveService - include ServicePushDataValidations - - prop_accessor :drone_url, :token - boolean_accessor :enable_ssl_verification - - validates :drone_url, presence: true, public_url: true, if: :activated? - validates :token, presence: true, if: :activated? - - after_save :compose_service_hook, if: :activated? - - def compose_service_hook - hook = service_hook || build_service_hook - # If using a service template, project may not be available - hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join if project - hook.enable_ssl_verification = !!enable_ssl_verification - hook.save - end - - def execute(data) - case data[:object_kind] - when 'push' - service_hook.execute(data) if push_valid?(data) - when 'merge_request' - service_hook.execute(data) if merge_request_valid?(data) - when 'tag_push' - service_hook.execute(data) if tag_push_valid?(data) - end - end - - def allow_target_ci? - true - end - - def self.supported_events - %w(push merge_request tag_push) - end - - def commit_status_path(sha, ref) - Gitlab::Utils.append_path( - drone_url, - "gitlab/#{project.full_path}/commits/#{sha}?branch=#{Addressable::URI.encode_component(ref.to_s)}&access_token=#{token}") - end - - def commit_status(sha, ref) - with_reactive_cache(sha, ref) { |cached| cached[:commit_status] } - end - - def calculate_reactive_cache(sha, ref) - response = Gitlab::HTTP.try_get(commit_status_path(sha, ref), - verify: enable_ssl_verification, - extra_log_info: { project_id: project_id }) - - status = - if response && response.code == 200 && response['status'] - case response['status'] - when 'killed' - :canceled - when 'failure', 'error' - # Because drone return error if some test env failed - :failed - else - response["status"] - end - else - :error - end - - { commit_status: status } - end - - def build_page(sha, ref) - Gitlab::Utils.append_path( - drone_url, - "gitlab/#{project.full_path}/redirect/commits/#{sha}?branch=#{Addressable::URI.encode_component(ref.to_s)}") - end - - def title - 'Drone' - end - - def description - s_('ProjectService|Run CI/CD pipelines with Drone.') - end - - def self.to_param - 'drone_ci' - end - - def help - s_('ProjectService|Run CI/CD pipelines with Drone.') - end - - def fields - [ - { type: 'text', name: 'token', help: s_('ProjectService|Token for the Drone project.'), required: true }, - { type: 'text', name: 'drone_url', title: s_('ProjectService|Drone server URL'), placeholder: 'http://drone.example.com', required: true }, - { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } - ] - end -end diff --git a/app/models/project_services/jenkins_service.rb b/app/models/project_services/jenkins_service.rb deleted file mode 100644 index 990a35cd617..00000000000 --- a/app/models/project_services/jenkins_service.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -class JenkinsService < CiService - include ActionView::Helpers::UrlHelper - - prop_accessor :jenkins_url, :project_name, :username, :password - - before_update :reset_password - - validates :jenkins_url, presence: true, addressable_url: true, if: :activated? - validates :project_name, presence: true, if: :activated? - validates :username, presence: true, if: ->(service) { service.activated? && service.password_touched? && service.password.present? } - - default_value_for :push_events, true - default_value_for :merge_requests_events, false - default_value_for :tag_push_events, false - - after_save :compose_service_hook, if: :activated? - - def reset_password - # don't reset the password if a new one is provided - if (jenkins_url_changed? || username.blank?) && !password_touched? - self.password = nil - end - end - - def compose_service_hook - hook = service_hook || build_service_hook - hook.url = hook_url - hook.save - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - service_hook.execute(data, "#{data[:object_kind]}_hook") - end - - def test(data) - begin - result = execute(data) - return { success: false, result: result[:message] } if result[:http_status] != 200 - rescue StandardError => error - return { success: false, result: error } - end - - { success: true, result: result[:message] } - end - - def hook_url - url = URI.parse(jenkins_url) - url.path = File.join(url.path || '/', "project/#{project_name}") - url.user = ERB::Util.url_encode(username) unless username.blank? - url.password = ERB::Util.url_encode(password) unless password.blank? - url.to_s - end - - def self.supported_events - %w(push merge_request tag_push) - end - - def title - 'Jenkins' - end - - def description - s_('Run CI/CD pipelines with Jenkins.') - end - - def help - docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('integration/jenkins'), target: '_blank', rel: 'noopener noreferrer' - s_('Run CI/CD pipelines with Jenkins when you push to a repository, or when a merge request is created, updated, or merged. %{docs_link}').html_safe % { docs_link: docs_link.html_safe } - end - - def self.to_param - 'jenkins' - end - - def fields - [ - { - type: 'text', - name: 'jenkins_url', - title: s_('ProjectService|Jenkins server URL'), - required: true, - placeholder: 'http://jenkins.example.com', - help: s_('The URL of the Jenkins server.') - }, - { - type: 'text', - name: 'project_name', - required: true, - placeholder: 'my_project_name', - help: s_('The name of the Jenkins project. Copy the name from the end of the URL to the project.') - }, - { - type: 'text', - name: 'username', - required: true, - help: s_('The username for the Jenkins server.') - }, - { - type: 'password', - name: 'password', - help: s_('The password for the Jenkins server.'), - non_empty_password_title: s_('ProjectService|Enter new password.'), - non_empty_password_help: s_('ProjectService|Leave blank to use your current password.') - } - ] - end -end diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb deleted file mode 100644 index bd6344c6e1a..00000000000 --- a/app/models/project_services/mock_ci_service.rb +++ /dev/null @@ -1,90 +0,0 @@ -# frozen_string_literal: true - -# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service -class MockCiService < CiService - ALLOWED_STATES = %w[failed canceled running pending success success-with-warnings skipped not_found].freeze - - prop_accessor :mock_service_url - validates :mock_service_url, presence: true, public_url: true, if: :activated? - - def title - 'MockCI' - end - - def description - 'Mock an external CI' - end - - def self.to_param - 'mock_ci' - end - - def fields - [ - { - type: 'text', - name: 'mock_service_url', - title: s_('ProjectService|Mock service URL'), - placeholder: 'http://localhost:4004', - required: true - } - ] - end - - # Return complete url to build page - # - # Ex. - # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c - # - def build_page(sha, ref) - Gitlab::Utils.append_path( - mock_service_url, - "#{project.namespace.path}/#{project.path}/status/#{sha}") - end - - # Return string with build status or :error symbol - # - # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' - # - # - # Ex. - # @service.commit_status('13be4ac', 'master') - # # => 'success' - # - # @service.commit_status('2abe4ac', 'dev') - # # => 'running' - # - # - def commit_status(sha, ref) - response = Gitlab::HTTP.get(commit_status_path(sha), verify: false) - read_commit_status(response) - rescue Errno::ECONNREFUSED - :error - end - - def commit_status_path(sha) - Gitlab::Utils.append_path( - mock_service_url, - "#{project.namespace.path}/#{project.path}/status/#{sha}.json") - end - - def read_commit_status(response) - return :error unless response.code == 200 || response.code == 404 - - status = if response.code == 404 - 'pending' - else - response['status'] - end - - if status.present? && ALLOWED_STATES.include?(status) - status - else - :error - end - end - - def can_test? - false - end -end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb deleted file mode 100644 index 6fc24a4778c..00000000000 --- a/app/models/project_services/teamcity_service.rb +++ /dev/null @@ -1,189 +0,0 @@ -# frozen_string_literal: true - -class TeamcityService < CiService - include ReactiveService - include ServicePushDataValidations - - prop_accessor :teamcity_url, :build_type, :username, :password - - validates :teamcity_url, presence: true, public_url: true, if: :activated? - validates :build_type, presence: true, if: :activated? - validates :username, - presence: true, - if: ->(service) { service.activated? && service.password } - validates :password, - presence: true, - if: ->(service) { service.activated? && service.username } - - attr_accessor :response - - after_save :compose_service_hook, if: :activated? - before_update :reset_password - - class << self - def to_param - 'teamcity' - end - - def supported_events - %w(push merge_request) - end - - def event_description(event) - case event - when 'push', 'push_events' - 'TeamCity CI will be triggered after every push to the repository except branch delete' - when 'merge_request', 'merge_request_events' - 'TeamCity CI will be triggered after a merge request has been created or updated' - end - end - end - - def compose_service_hook - hook = service_hook || build_service_hook - hook.save - end - - def reset_password - if teamcity_url_changed? && !password_touched? - self.password = nil - end - end - - def title - 'JetBrains TeamCity' - end - - def description - s_('ProjectService|Run CI/CD pipelines with JetBrains TeamCity.') - end - - def help - s_('To run CI/CD pipelines with JetBrains TeamCity, input the GitLab project details in the TeamCity project Version Control Settings.') - end - - def fields - [ - { - type: 'text', - name: 'teamcity_url', - title: s_('ProjectService|TeamCity server URL'), - placeholder: 'https://teamcity.example.com', - required: true - }, - { - type: 'text', - name: 'build_type', - help: s_('ProjectService|The build configuration ID of the TeamCity project.'), - required: true - }, - { - type: 'text', - name: 'username', - help: s_('ProjectService|Must have permission to trigger a manual build in TeamCity.') - }, - { - type: 'password', - name: 'password', - non_empty_password_title: s_('ProjectService|Enter new password'), - non_empty_password_help: s_('ProjectService|Leave blank to use your current password') - } - ] - end - - def build_page(sha, ref) - with_reactive_cache(sha, ref) {|cached| cached[:build_page] } - end - - def commit_status(sha, ref) - with_reactive_cache(sha, ref) {|cached| cached[:commit_status] } - end - - def calculate_reactive_cache(sha, ref) - response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,revision:#{sha}") - - if response - { build_page: read_build_page(response), commit_status: read_commit_status(response) } - else - { build_page: teamcity_url, commit_status: :error } - end - end - - def execute(data) - case data[:object_kind] - when 'push' - execute_push(data) - when 'merge_request' - execute_merge_request(data) - end - end - - private - - def execute_push(data) - branch = Gitlab::Git.ref_name(data[:ref]) - post_to_build_queue(data, branch) if push_valid?(data) - end - - def execute_merge_request(data) - branch = data[:object_attributes][:source_branch] - post_to_build_queue(data, branch) if merge_request_valid?(data) - end - - def read_build_page(response) - if response.code != 200 - # If actual build link can't be determined, - # send user to build summary page. - build_url("viewLog.html?buildTypeId=#{build_type}") - else - # If actual build link is available, go to build result page. - built_id = response['build']['id'] - build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}") - end - end - - def read_commit_status(response) - return :error unless response.code == 200 || response.code == 404 - - status = if response.code == 404 - 'Pending' - else - response['build']['status'] - end - - return :error unless status.present? - - if status.include?('SUCCESS') - 'success' - elsif status.include?('FAILURE') - 'failed' - elsif status.include?('Pending') - 'pending' - else - :error - end - end - - def build_url(path) - Gitlab::Utils.append_path(teamcity_url, path) - end - - def get_path(path) - Gitlab::HTTP.try_get(build_url(path), verify: false, basic_auth: basic_auth, extra_log_info: { project_id: project_id }) - end - - def post_to_build_queue(data, branch) - Gitlab::HTTP.post( - build_url('httpAuth/app/rest/buildQueue'), - body: "<build branchName=#{branch.encode(xml: :attr)}>"\ - "<buildType id=#{build_type.encode(xml: :attr)}/>"\ - '</build>', - headers: { 'Content-type' => 'application/xml' }, - basic_auth: basic_auth - ) - end - - def basic_auth - { username: username, password: password } - end -end diff --git a/app/models/snippet_repository_storage_move.rb b/app/models/snippet_repository_storage_move.rb deleted file mode 100644 index 8234905a7e1..00000000000 --- a/app/models/snippet_repository_storage_move.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -# This is a compatibility class to avoid calling a non-existent -# class from sidekiq during deployment. -# -# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853. -# we cannot remove this class entirely because there can be jobs -# referencing it. -# -# We can get rid of this class in 14.0 -# https://gitlab.com/gitlab-org/gitlab/-/issues/322393 -class SnippetRepositoryStorageMove < Snippets::RepositoryStorageMove -end diff --git a/app/services/branches/create_service.rb b/app/services/branches/create_service.rb index 8684da701db..848e6aaa65a 100644 --- a/app/services/branches/create_service.rb +++ b/app/services/branches/create_service.rb @@ -2,8 +2,8 @@ module Branches class CreateService < BaseService - def execute(branch_name, ref, create_master_if_empty: true) - create_master_branch if create_master_if_empty && project.empty_repo? + def execute(branch_name, ref, create_default_branch_if_empty: true) + create_default_branch if create_default_branch_if_empty && project.empty_repo? result = ::Branches::ValidateNewService.new(project).execute(branch_name) @@ -27,13 +27,13 @@ module Branches private - def create_master_branch + def create_default_branch project.repository.create_file( current_user, '/README.md', '', message: 'Add README.md', - branch_name: 'master' + branch_name: project.default_branch_or_main ) end end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index a70679dab5f..26291c0358e 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -25,14 +25,14 @@ %span.access-request-links.gl-ml-3 = render 'shared/members/access_request_links', source: @project - - if @project.tag_list.present? - = cache_if(cache_enabled, [@project, :tag_list], expires_in: 1.day) do + - if @project.topic_list.present? + = cache_if(cache_enabled, [@project, :topic_list], expires_in: 1.day) do %span.home-panel-topic-list.mt-2.w-100.d-inline-flex.gl-font-base.gl-font-weight-normal.gl-align-items-center = sprite_icon('tag', css_class: 'icon gl-relative gl-mr-2') - @project.topics_to_show.each do |topic| - project_topics_classes = "badge badge-pill badge-secondary gl-mr-2" - - explore_project_topic_path = explore_projects_path(tag: topic) + - explore_project_topic_path = explore_projects_path(topic: topic) - if topic.length > max_project_topic_length %a{ class: "#{ project_topics_classes } str-truncated-30 has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path, itemprop: 'keywords' } = topic.titleize diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index a5414ce7ef2..548fdc10bd4 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -46,35 +46,37 @@ title: s_('Branches|Compare') do = s_('Branches|Compare') - = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name] + = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name], class: 'gl-vertical-align-top' - if can?(current_user, :push_code, @project) - if branch.name == @project.repository.root_ref - delete_default_branch_tooltip = s_('Branches|The default branch cannot be deleted') - %span.has-tooltip{ title: delete_default_branch_tooltip } - %button{ class: "gl-button btn btn-danger disabled", disabled: true, 'aria-label' => delete_default_branch_tooltip } - = sprite_icon("remove") + %span.gl-display-inline-block.has-tooltip{ title: delete_default_branch_tooltip } + %button{ class: 'gl-button btn btn-default btn-icon disabled', disabled: true, 'aria-label' => delete_default_branch_tooltip } + = sprite_icon('remove', css_class: 'gl-button-icon gl-icon') - elsif protected_branch?(@project, branch) - if can?(current_user, :push_to_delete_protected_branch, @project) - %button{ class: "gl-button btn btn-danger has-tooltip", - title: s_('Branches|Delete protected branch'), - data: { toggle: "modal", - target: "#modal-delete-branch", + - delete_protected_branch_tooltip = s_('Branches|Delete protected branch') + %button{ class: 'gl-button btn btn-default btn-icon has-tooltip', + title: delete_protected_branch_tooltip, + 'aria-label' => delete_protected_branch_tooltip, + data: { toggle: 'modal', + target: '#modal-delete-branch', delete_path: project_branch_path(@project, branch.name), branch_name: branch.name, - is_merged: ("true" if merged) } } - = sprite_icon("remove") + is_merged: ('true' if merged) } } + = sprite_icon('remove', css_class: 'gl-button-icon gl-icon') - else - - delete_protected_branch_tooltip = s_('Branches|Only a project maintainer or owner can delete a protected branch') - %span.has-tooltip{ title: delete_protected_branch_tooltip } - %button{ class: "gl-button btn btn-danger disabled", disabled: true, 'aria-label' => delete_protected_branch_tooltip } - = sprite_icon("remove") + - delete_protected_branch_disabled_tooltip = s_('Branches|Only a project maintainer or owner can delete a protected branch') + %span.has-tooltip{ title: delete_protected_branch_disabled_tooltip } + %button{ class: 'gl-button btn btn-default btn-icon disabled', disabled: true, 'aria-label' => delete_protected_branch_disabled_tooltip, data: { testid: 'remove-protected-branch' } } + = sprite_icon('remove', css_class: 'gl-button-icon gl-icon') - else = link_to project_branch_path(@project, branch.name), - class: "gl-button btn btn-danger js-remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip", + class: 'gl-button btn btn-default btn-icon js-remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip', title: s_('Branches|Delete branch'), method: :delete, data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } }, remote: true, 'aria-label' => s_('Branches|Delete branch') do - = sprite_icon("remove") + = sprite_icon('remove', css_class: 'gl-button-icon gl-icon') diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 2f89a3f62ed..2d32e07d379 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -2,11 +2,11 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - archive_prefix = "#{project.path}-#{ref.tr('/', '-')}" - .project-action-button.dropdown.inline> - %button.gl-button.btn.btn-default.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static', data: { qa_selector: 'download_source_code_button' } } - = sprite_icon('download', css_class: 'gl-icon') + .project-action-button.dropdown.gl-new-dropdown.inline> + %button.gl-button.btn.btn-default.dropdown-toggle.gl-dropdown-toggle.dropdown-icon-only.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static', data: { qa_selector: 'download_source_code_button' } } + = sprite_icon('download', css_class: 'gl-icon dropdown-icon') %span.sr-only= _('Select Archive Format') - = sprite_icon('chevron-down', css_class: 'gl-icon') + = sprite_icon('chevron-down', css_class: 'gl-icon dropdown-chevron') .dropdown-menu.dropdown-menu-right{ role: 'menu' } %section %h5.m-0.dropdown-bold-header= _('Download source code') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index ea0845b00e8..04ac7036752 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2570,15 +2570,6 @@ :weight: 1 :idempotent: :tags: [] -- :name: project_schedule_bulk_repository_shard_moves - :worker_name: ProjectScheduleBulkRepositoryShardMovesWorker - :feature_category: :gitaly - :has_external_dependencies: - :urgency: :throttled - :resource_boundary: :unknown - :weight: 1 - :idempotent: true - :tags: [] - :name: project_service :worker_name: ProjectServiceWorker :feature_category: :integrations @@ -2588,15 +2579,6 @@ :weight: 1 :idempotent: :tags: [] -- :name: project_update_repository_storage - :worker_name: ProjectUpdateRepositoryStorageWorker - :feature_category: :gitaly - :has_external_dependencies: - :urgency: :throttled - :resource_boundary: :unknown - :weight: 1 - :idempotent: true - :tags: [] - :name: projects_git_garbage_collect :worker_name: Projects::GitGarbageCollectWorker :feature_category: :gitaly @@ -2811,24 +2793,6 @@ :weight: 1 :idempotent: :tags: [] -- :name: snippet_schedule_bulk_repository_shard_moves - :worker_name: SnippetScheduleBulkRepositoryShardMovesWorker - :feature_category: :gitaly - :has_external_dependencies: - :urgency: :throttled - :resource_boundary: :unknown - :weight: 1 - :idempotent: true - :tags: [] -- :name: snippet_update_repository_storage - :worker_name: SnippetUpdateRepositoryStorageWorker - :feature_category: :gitaly - :has_external_dependencies: - :urgency: :throttled - :resource_boundary: :unknown - :weight: 1 - :idempotent: true - :tags: [] - :name: snippets_schedule_bulk_repository_shard_moves :worker_name: Snippets::ScheduleBulkRepositoryShardMovesWorker :feature_category: :gitaly diff --git a/app/workers/project_schedule_bulk_repository_shard_moves_worker.rb b/app/workers/project_schedule_bulk_repository_shard_moves_worker.rb deleted file mode 100644 index 23d1594e4d9..00000000000 --- a/app/workers/project_schedule_bulk_repository_shard_moves_worker.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# This is a compatibility class to avoid calling a non-existent -# class from sidekiq during deployment. -# -# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853. -# we cannot remove this class entirely because there can be jobs -# referencing it. -# -# We can get rid of this class in 14.0 -# https://gitlab.com/gitlab-org/gitlab/-/issues/322393 -class ProjectScheduleBulkRepositoryShardMovesWorker < Projects::ScheduleBulkRepositoryShardMovesWorker - idempotent! - urgency :throttled -end diff --git a/app/workers/project_update_repository_storage_worker.rb b/app/workers/project_update_repository_storage_worker.rb deleted file mode 100644 index 0d68c0e16f8..00000000000 --- a/app/workers/project_update_repository_storage_worker.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# This is a compatibility class to avoid calling a non-existent -# class from sidekiq during deployment. -# -# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853. -# we cannot remove this class entirely because there can be jobs -# referencing it. -# -# We can get rid of this class in 14.0 -# https://gitlab.com/gitlab-org/gitlab/-/issues/322393 -class ProjectUpdateRepositoryStorageWorker < Projects::UpdateRepositoryStorageWorker - idempotent! - urgency :throttled -end diff --git a/app/workers/snippet_schedule_bulk_repository_shard_moves_worker.rb b/app/workers/snippet_schedule_bulk_repository_shard_moves_worker.rb deleted file mode 100644 index 94a6b22538b..00000000000 --- a/app/workers/snippet_schedule_bulk_repository_shard_moves_worker.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -# This is a compatibility class to avoid calling a non-existent -# class from sidekiq during deployment. -# -# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853. -# we cannot remove this class entirely because there can be jobs -# referencing it. -# -# We can get rid of this class in 14.0 -# https://gitlab.com/gitlab-org/gitlab/-/issues/322393 -class SnippetScheduleBulkRepositoryShardMovesWorker < Snippets::ScheduleBulkRepositoryShardMovesWorker - idempotent! - feature_category :gitaly - urgency :throttled -end diff --git a/app/workers/snippet_update_repository_storage_worker.rb b/app/workers/snippet_update_repository_storage_worker.rb deleted file mode 100644 index befae6db4f4..00000000000 --- a/app/workers/snippet_update_repository_storage_worker.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# This is a compatibility class to avoid calling a non-existent -# class from sidekiq during deployment. -# -# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853. -# we cannot remove this class entirely because there can be jobs -# referencing it. -# -# We can get rid of this class in 14.0 -# https://gitlab.com/gitlab-org/gitlab/-/issues/322393 -class SnippetUpdateRepositoryStorageWorker < Snippets::UpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker - idempotent! - urgency :throttled -end diff --git a/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml b/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml deleted file mode 100644 index ef4d1cb3bfe..00000000000 --- a/config/feature_flags/development/security_dast_site_profiles_additional_fields.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: security_dast_site_profiles_additional_fields -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46848 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292897 -milestone: '13.7' -type: development -group: group::dynamic analysis -default_enabled: true diff --git a/config/feature_flags/development/security_dast_site_profiles_api_option.yml b/config/feature_flags/development/security_dast_site_profiles_api_option.yml deleted file mode 100644 index 8a9c6fefb5d..00000000000 --- a/config/feature_flags/development/security_dast_site_profiles_api_option.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: security_dast_site_profiles_api_option -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58723 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/325130 -milestone: '13.12' -type: development -group: group::dynamic analysis -default_enabled: true diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 24d140abada..06f9cc2cd85 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -298,14 +298,10 @@ - 1 - - project_import_schedule - 1 -- - project_schedule_bulk_repository_shard_moves - - 1 - - project_service - 1 - - project_template_export - 1 -- - project_update_repository_storage - - 1 - - projects_git_garbage_collect - 1 - - projects_post_creation @@ -370,10 +366,6 @@ - 1 - - set_user_status_based_on_user_cap_setting - 1 -- - snippet_schedule_bulk_repository_shard_moves - - 1 -- - snippet_update_repository_storage - - 1 - - snippets_schedule_bulk_repository_shard_moves - 1 - - snippets_update_repository_storage diff --git a/db/migrate/20210519132109_initialize_conversion_of_ci_builds_metadata_to_bigint.rb b/db/migrate/20210519132109_initialize_conversion_of_ci_builds_metadata_to_bigint.rb new file mode 100644 index 00000000000..7ff0276b4b5 --- /dev/null +++ b/db/migrate/20210519132109_initialize_conversion_of_ci_builds_metadata_to_bigint.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class InitializeConversionOfCiBuildsMetadataToBigint < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + TABLE = :ci_builds_metadata + COLUMN = :build_id + + def up + initialize_conversion_of_integer_to_bigint(TABLE, COLUMN) + end + + def down + revert_initialize_conversion_of_integer_to_bigint(TABLE, COLUMN) + end +end diff --git a/db/post_migrate/20210519132129_backfill_ci_builds_metadata_for_bigint_conversion.rb b/db/post_migrate/20210519132129_backfill_ci_builds_metadata_for_bigint_conversion.rb new file mode 100644 index 00000000000..3aeabbcc0ad --- /dev/null +++ b/db/post_migrate/20210519132129_backfill_ci_builds_metadata_for_bigint_conversion.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class BackfillCiBuildsMetadataForBigintConversion < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + TABLE = :ci_builds_metadata + COLUMN = :build_id + + def up + backfill_conversion_of_integer_to_bigint TABLE, COLUMN, batch_size: 15000, sub_batch_size: 100 + end + + def down + revert_backfill_conversion_of_integer_to_bigint TABLE, COLUMN + end +end diff --git a/db/post_migrate/20210520012430_backfill_pk_conversion_for_self_managed.rb b/db/post_migrate/20210520012430_backfill_pk_conversion_for_self_managed.rb new file mode 100644 index 00000000000..d554b412420 --- /dev/null +++ b/db/post_migrate/20210520012430_backfill_pk_conversion_for_self_managed.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class BackfillPkConversionForSelfManaged < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + CONVERSIONS = [ + { table: :events, columns: %i(id), sub_batch_size: 500 }, + { table: :push_event_payloads, columns: %i(event_id), sub_batch_size: 2500, primary_key: :event_id }, + { table: :ci_job_artifacts, columns: %i(id job_id), sub_batch_size: 2000 }, + { table: :ci_sources_pipelines, columns: %i(source_job_id), sub_batch_size: 100 }, + { table: :ci_build_needs, columns: %i(build_id), sub_batch_size: 1000 }, + { table: :ci_builds, columns: %i(id stage_id), sub_batch_size: 250 }, + { table: :ci_builds_runner_session, columns: %i(build_id), sub_batch_size: 5000 }, + { table: :ci_build_trace_chunks, columns: %i(build_id), sub_batch_size: 1000 } + ] + + def up + return unless should_run? + + CONVERSIONS.each do |conversion| + backfill_conversion_of_integer_to_bigint( + conversion[:table], conversion[:columns], + sub_batch_size: conversion[:sub_batch_size], primary_key: conversion.fetch(:primary_key, :id) + ) + end + end + + def down + return unless should_run? + + CONVERSIONS.each do |conversion| + revert_backfill_conversion_of_integer_to_bigint( + conversion[:table], conversion[:columns], + primary_key: conversion.fetch(:primary_key, :id) + ) + end + end + + private + + def should_run? + !Gitlab.com? + end +end diff --git a/db/schema_migrations/20210519132109 b/db/schema_migrations/20210519132109 new file mode 100644 index 00000000000..9d8537aa6b2 --- /dev/null +++ b/db/schema_migrations/20210519132109 @@ -0,0 +1 @@ +ba464ad09f3cec0e9cf94b3041ad946e3a5a8c915ce0b9f4f95ab49cb55d305d
\ No newline at end of file diff --git a/db/schema_migrations/20210519132129 b/db/schema_migrations/20210519132129 new file mode 100644 index 00000000000..da7a8212092 --- /dev/null +++ b/db/schema_migrations/20210519132129 @@ -0,0 +1 @@ +8041e898177bdee3b4d1ad82ec7dd3b79cb7dd740f773cd91dc4306a87a397fd
\ No newline at end of file diff --git a/db/schema_migrations/20210520012430 b/db/schema_migrations/20210520012430 new file mode 100644 index 00000000000..d92bc8d8369 --- /dev/null +++ b/db/schema_migrations/20210520012430 @@ -0,0 +1 @@ +86b9f1c0f4288bf83e8b2d70b06b8951b7bcef0aa9324d9546471f6f094b014b
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 8bfe5e16f41..7e2a540c6f1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -152,6 +152,15 @@ BEGIN END; $$; +CREATE FUNCTION trigger_8487d4de3e7b() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW."build_id_convert_to_bigint" := NEW."build_id"; + RETURN NEW; +END; +$$; + CREATE FUNCTION trigger_91dc388a5fe6() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -10585,7 +10594,8 @@ CREATE TABLE ci_builds_metadata ( has_exposed_artifacts boolean, environment_auto_stop_in character varying(255), expanded_environment_name character varying(255), - secrets jsonb DEFAULT '{}'::jsonb NOT NULL + secrets jsonb DEFAULT '{}'::jsonb NOT NULL, + build_id_convert_to_bigint bigint DEFAULT 0 NOT NULL ); CREATE SEQUENCE ci_builds_metadata_id_seq @@ -25249,6 +25259,8 @@ CREATE TRIGGER trigger_69523443cc10 BEFORE INSERT OR UPDATE ON events FOR EACH R CREATE TRIGGER trigger_8485e97c00e3 BEFORE INSERT OR UPDATE ON ci_sources_pipelines FOR EACH ROW EXECUTE FUNCTION trigger_8485e97c00e3(); +CREATE TRIGGER trigger_8487d4de3e7b BEFORE INSERT OR UPDATE ON ci_builds_metadata FOR EACH ROW EXECUTE FUNCTION trigger_8487d4de3e7b(); + CREATE TRIGGER trigger_91dc388a5fe6 BEFORE INSERT OR UPDATE ON ci_build_trace_sections FOR EACH ROW EXECUTE FUNCTION trigger_91dc388a5fe6(); CREATE TRIGGER trigger_be1804f21693 BEFORE INSERT OR UPDATE ON ci_job_artifacts FOR EACH ROW EXECUTE FUNCTION trigger_be1804f21693(); diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md index dd43c7d6fbb..72ab536ceae 100644 --- a/doc/administration/monitoring/performance/performance_bar.md +++ b/doc/administration/monitoring/performance/performance_bar.md @@ -68,7 +68,8 @@ appears next to requests with warnings. ## Enable the Performance Bar via the Admin Area -The GitLab Performance Bar is disabled by default. To enable it for a given group: +The GitLab Performance Bar is disabled by default for non-administrators. To enable it +for a given group: 1. Sign in as a user with Administrator [permissions](../../../user/permissions.md). 1. In the menu bar, click **Admin Area**. diff --git a/doc/api/discussions.md b/doc/api/discussions.md index 3d40349ecca..d8d989adfe2 100644 --- a/doc/api/discussions.md +++ b/doc/api/discussions.md @@ -156,7 +156,7 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `issue_iid` | integer | yes | The IID of an issue | | `body` | string | yes | The content of the thread | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/issues/11/discussions?body=comment" @@ -167,7 +167,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla Adds a new note to the thread. This can also [create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment). **WARNING** -Notes can be added to other items than comments (system notes, etc.) making them threads. +Notes can be added to other items than comments, such as system notes, making them threads. ```plaintext POST /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes @@ -182,7 +182,7 @@ Parameters: | `discussion_id` | integer | yes | The ID of a thread | | `note_id` | integer | yes | The ID of a thread note | | `body` | string | yes | The content of the note/reply | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment" @@ -365,7 +365,7 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `snippet_id` | integer | yes | The ID of an snippet | | `body` | string | yes | The content of a discussion | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions?body=comment" @@ -388,7 +388,7 @@ Parameters: | `discussion_id` | integer | yes | The ID of a thread | | `note_id` | integer | yes | The ID of a thread note | | `body` | string | yes | The content of the note/reply | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment" @@ -572,7 +572,7 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) | | `epic_id` | integer | yes | The ID of an epic | | `body` | string | yes | The content of the thread | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/epics/11/discussions?body=comment" @@ -596,7 +596,7 @@ Parameters: | `discussion_id` | integer | yes | The ID of a thread | | `note_id` | integer | yes | The ID of a thread note | | `body` | string | yes | The content of the note/reply | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment" @@ -847,7 +847,7 @@ Parameters for all comments: | `merge_request_iid` | integer | yes | The IID of a merge request | | `body` | string | yes | The content of the thread | | `commit_id` | string | no | SHA referencing commit to start this thread on | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | | `position` | hash | no | Position when creating a diff note | | `position[base_sha]` | string | yes | Base commit SHA in the source branch | | `position[start_sha]` | string | yes | SHA referencing commit in target branch | @@ -902,7 +902,7 @@ To create a new thread: "previous versions are here" ] ``` - + 1. Create a new diff thread. This example creates a thread on an added line: ```shell @@ -981,7 +981,7 @@ Parameters: | `discussion_id` | integer | yes | The ID of a thread | | `note_id` | integer | yes | The ID of a thread note | | `body` | string | yes | The content of the note/reply | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment" @@ -1216,7 +1216,7 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `commit_id` | integer | yes | The ID of a commit | | `body` | string | yes | The content of the thread | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such as `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | | `position` | hash | no | Position when creating a diff note | | `position[base_sha]` | string | yes | Base commit SHA in the source branch | | `position[start_sha]` | string | yes | SHA referencing commit in target branch | @@ -1252,7 +1252,7 @@ Parameters: | `discussion_id` | integer | yes | The ID of a thread | | `note_id` | integer | yes | The ID of a thread note | | `body` | string | yes | The content of the note/reply | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires administrator or project/group owner rights) | +| `created_at` | string | no | Date time string, ISO 8601 formatted, such `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 05c2fdf26fd..0ac23edf687 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1533,13 +1533,13 @@ Input type: `DastSiteProfileCreateInput` | Name | Type | Description | | ---- | ---- | ----------- | -| <a id="mutationdastsiteprofilecreateauth"></a>`auth` | [`DastSiteProfileAuthInput`](#dastsiteprofileauthinput) | Parameters for authentication. Will be ignored if `security_dast_site_profiles_additional_fields` feature flag is disabled. | +| <a id="mutationdastsiteprofilecreateauth"></a>`auth` | [`DastSiteProfileAuthInput`](#dastsiteprofileauthinput) | Parameters for authentication. | | <a id="mutationdastsiteprofilecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| <a id="mutationdastsiteprofilecreateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. Defaults to `[]`. Will be ignored if `security_dast_site_profiles_additional_fields` feature flag is disabled. | +| <a id="mutationdastsiteprofilecreateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. Defaults to `[]`. | | <a id="mutationdastsiteprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site profile belongs to. | | <a id="mutationdastsiteprofilecreateprofilename"></a>`profileName` | [`String!`](#string) | The name of the site profile. | -| <a id="mutationdastsiteprofilecreaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. Will be ignored if `security_dast_site_profiles_additional_fields` feature flag is disabled. | -| <a id="mutationdastsiteprofilecreatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. Will be ignored if `security_dast_site_profiles_api_option` feature flag is disabled. | +| <a id="mutationdastsiteprofilecreaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | +| <a id="mutationdastsiteprofilecreatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. | | <a id="mutationdastsiteprofilecreatetargeturl"></a>`targetUrl` | [`String`](#string) | The URL of the target to be scanned. | #### Fields @@ -1577,14 +1577,14 @@ Input type: `DastSiteProfileUpdateInput` | Name | Type | Description | | ---- | ---- | ----------- | -| <a id="mutationdastsiteprofileupdateauth"></a>`auth` | [`DastSiteProfileAuthInput`](#dastsiteprofileauthinput) | Parameters for authentication. Will be ignored if `security_dast_site_profiles_additional_fields` feature flag is disabled. | +| <a id="mutationdastsiteprofileupdateauth"></a>`auth` | [`DastSiteProfileAuthInput`](#dastsiteprofileauthinput) | Parameters for authentication. | | <a id="mutationdastsiteprofileupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| <a id="mutationdastsiteprofileupdateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. Will be ignored if `security_dast_site_profiles_additional_fields` feature flag is disabled. | +| <a id="mutationdastsiteprofileupdateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. | | <a id="mutationdastsiteprofileupdatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site profile belongs to. | | <a id="mutationdastsiteprofileupdateid"></a>`id` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile to be updated. | | <a id="mutationdastsiteprofileupdateprofilename"></a>`profileName` | [`String!`](#string) | The name of the site profile. | -| <a id="mutationdastsiteprofileupdaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. Will be ignored if `security_dast_site_profiles_additional_fields` feature flag is disabled. | -| <a id="mutationdastsiteprofileupdatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. Will be ignored if `security_dast_site_profiles_api_option` feature flag is disabled. | +| <a id="mutationdastsiteprofileupdaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | +| <a id="mutationdastsiteprofileupdatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. | | <a id="mutationdastsiteprofileupdatetargeturl"></a>`targetUrl` | [`String`](#string) | The URL of the target to be scanned. | #### Fields @@ -7857,15 +7857,15 @@ Represents a DAST Site Profile. | Name | Type | Description | | ---- | ---- | ----------- | -| <a id="dastsiteprofileauth"></a>`auth` | [`DastSiteProfileAuth`](#dastsiteprofileauth) | Target authentication details. Will always return `null` if `security_dast_site_profiles_additional_fields` feature flag is disabled. | +| <a id="dastsiteprofileauth"></a>`auth` | [`DastSiteProfileAuth`](#dastsiteprofileauth) | Target authentication details. | | <a id="dastsiteprofileeditpath"></a>`editPath` | [`String`](#string) | Relative web path to the edit page of a site profile. | -| <a id="dastsiteprofileexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. Will always return `null` if `security_dast_site_profiles_additional_fields` feature flag is disabled. | +| <a id="dastsiteprofileexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. | | <a id="dastsiteprofileid"></a>`id` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile. | | <a id="dastsiteprofilenormalizedtargeturl"></a>`normalizedTargetUrl` | [`String`](#string) | Normalized URL of the target to be scanned. | | <a id="dastsiteprofileprofilename"></a>`profileName` | [`String`](#string) | The name of the site profile. | | <a id="dastsiteprofilereferencedinsecuritypolicies"></a>`referencedInSecurityPolicies` | [`[String!]`](#string) | List of security policy names that are referencing given project. | -| <a id="dastsiteprofilerequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. Will always return `null` if `security_dast_site_profiles_additional_fields` feature flag is disabled. | -| <a id="dastsiteprofiletargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. Will always return `null` if `security_dast_site_profiles_api_option` feature flag is disabled. | +| <a id="dastsiteprofilerequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. | +| <a id="dastsiteprofiletargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. | | <a id="dastsiteprofiletargeturl"></a>`targetUrl` | [`String`](#string) | The URL of the target to be scanned. | | <a id="dastsiteprofileuserpermissions"></a>`userPermissions` | [`DastSiteProfilePermissions!`](#dastsiteprofilepermissions) | Permissions for the current user on the resource. | | <a id="dastsiteprofilevalidationstatus"></a>`validationStatus` | [`DastSiteProfileValidationStatusEnum`](#dastsiteprofilevalidationstatusenum) | The current validation status of the site profile. | diff --git a/doc/api/project_aliases.md b/doc/api/project_aliases.md index 439f34ad93b..e09bb167a31 100644 --- a/doc/api/project_aliases.md +++ b/doc/api/project_aliases.md @@ -68,8 +68,8 @@ Example response: ## Create a project alias -Add a new alias for a project. Responds with a 201 when successful, -400 when there are validation errors (e.g. alias already exists): +Add a new alias for a project. When successful, responds with `201 Created`. +When there are validation errors, for example, when the alias already exists, responds with `400 Bad Request`: ```plaintext POST /project_aliases diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index a4ad496b667..9f04afaa713 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -18,7 +18,7 @@ See also: Start a new export. -The endpoint also accepts an `upload` parameter. This parameter is a hash that contains +The endpoint also accepts an `upload` parameter. This parameter is a hash. It contains all the necessary information to upload the exported project to a web server or to any S3-compatible platform. At the moment we only support binary data file uploads to the final server. @@ -70,23 +70,14 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a Status can be one of: -- `none` -- `queued` -- `started` -- `finished` -- `regeneration_in_progress` - -`queued` state represents the request for export is received, and is currently in the queue to be processed. - -The `started` state represents that the export process has started and is currently in progress. -It includes the process of exporting, actions performed on the resultant file such as sending -an email notifying the user to download the file, uploading the exported file to a web server, etc. - -`finished` state is after the export process has completed and the user has been notified. - -`regeneration_in_progress` is when an export file is available to download, and a request to generate a new export is in process. - -`none` is when there are no exports _queued_, _started_, _finished_, or _being regenerated_ +- `none`: No exports _queued_, _started_, _finished_, or _being regenerated_. +- `queued`: The request for export is received, and is in the queue to be processed. +- `started`: The export process has started and is in progress. It includes: + - The process of exporting. + - Actions performed on the resulting file, such as sending an email notifying + the user to download the file, or uploading the exported file to a web server. +- `finished`: After the export process has completed and the user has been notified. +- `regeneration_in_progress`: An export file is available to download, and a request to generate a new export is in process. `_links` are only present when export has finished. @@ -288,7 +279,7 @@ NOTE: An element's `id` field in `failed_relations` references the failure record, not the relation. NOTE: -The `failed_relations` array is currently capped to 100 items. +The `failed_relations` array is capped to 100 items. ```json { diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 70b804c368e..2ad8073b409 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -253,7 +253,7 @@ error message. Possible causes for a failed commit include: user tried to make an empty commit; - the branch was updated by a Git push while the file edit was in progress. -Currently GitLab Shell has a boolean return code, preventing GitLab from specifying the error. +GitLab Shell has a boolean return code, preventing GitLab from specifying the error. ## Delete existing file in repository diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 05aa003d89e..31bea68362a 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -229,6 +229,7 @@ To add a redirect: ```markdown --- redirect_to: '../newpath/to/file/index.md' + remove_date: 'YYYY-MM-DD' --- This document was moved to [another location](../path/to/file/index.md). diff --git a/doc/development/i18n/merging_translations.md b/doc/development/i18n/merging_translations.md index a3c6fc86693..d88b5039204 100644 --- a/doc/development/i18n/merging_translations.md +++ b/doc/development/i18n/merging_translations.md @@ -82,3 +82,9 @@ There's no automated way to pull the translation levels from CrowdIn, to display this information in the language selection dropdown. Therefore, the translation levels are hard-coded in the `TRANSLATION_LEVELS` constant in [`i18n.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/i18n.rb), and must be regularly updated. + +To update the translation levels: + +1. Get the translation levels (percentage of approved words) from [Crowdin](https://crowdin.com/project/gitlab-ee/settings#translations). + +1. Update the hard-coded translation levels in [`i18n.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/i18n.rb#L40). diff --git a/doc/development/snowplow.md b/doc/development/snowplow.md index 59ae1fc0d8c..aa1733fd42a 100644 --- a/doc/development/snowplow.md +++ b/doc/development/snowplow.md @@ -1,6 +1,6 @@ --- redirect_to: 'snowplow/index.md' -remove_date: '2021-06-31' +remove_date: '2021-06-30' --- This document was moved to [another location](snowplow/index.md). diff --git a/doc/user/project/repository/repository_mirroring.md b/doc/user/project/repository/repository_mirroring.md index 4df64463e52..6b79464005d 100644 --- a/doc/user/project/repository/repository_mirroring.md +++ b/doc/user/project/repository/repository_mirroring.md @@ -8,7 +8,7 @@ disqus_identifier: 'https://docs.gitlab.com/ee/workflow/repository_mirroring.htm # Repository mirroring **(FREE)** Repository mirroring allows for the mirroring of repositories to and from external sources. You -can use it to mirror branches, tags, and commits between repositories. It's useful when you want to use +can use it to mirror branches, tags, and commits between repositories. It helps you use a repository outside of GitLab. A repository mirror at GitLab updates automatically. You can also manually trigger an update @@ -36,7 +36,7 @@ Maintainer or Owner permissions to the mirrored project. The following are some possible use cases for repository mirroring: - You migrated to GitLab but still need to keep your project in another source. In that case, you - can simply set it up to mirror to GitLab (pull) and all the essential history of commits, tags, + can set it up to mirror to GitLab (pull) and all the essential history of commits, tags, and branches are available in your GitLab instance. **(PREMIUM)** - You have old projects in another source that you don't use actively anymore, but don't want to remove for archiving purposes. In that case, you can create a push mirror so that your active @@ -105,7 +105,7 @@ reflects that `develop` has diverged and was skipped, and be marked as a failed update. NOTE: -After the mirror is created, this option can currently only be modified via the [API](../../../api/remote_mirrors.md). +After the mirror is created, this option can only be modified via the [API](../../../api/remote_mirrors.md). ### Setting up a push mirror from GitLab to GitHub @@ -122,11 +122,15 @@ The repository pushes shortly thereafter. To force a push, select the **Update n ### Setting up a push mirror from GitLab to AWS CodeCommit -AWS CodeCommit push mirroring is currently the best way to connect GitLab repositories to AWS CodePipeline, as GitLab isn't yet supported as one of their Source Code Management (SCM) providers. +AWS CodeCommit push mirroring is the best way to connect GitLab repositories to +AWS CodePipeline, as GitLab isn't yet supported as one of their Source Code Management (SCM) providers. -Each new AWS CodePipeline needs significant AWS infrastructure setup. It also requires an individual pipeline per branch. +Each new AWS CodePipeline needs significant AWS infrastructure setup. It also +requires an individual pipeline per branch. -If AWS CodeDeploy is the final step of a CodePipeline, you can, instead, leverage GitLab CI/CD pipelines and simply use the AWS CLI in the final job in `.gitlab-ci.yml` to deploy to CodeDeploy. +If AWS CodeDeploy is the final step of a CodePipeline, you can, instead, leverage +GitLab CI/CD pipelines and use the AWS CLI in the final job in `.gitlab-ci.yml` +to deploy to CodeDeploy. NOTE: GitLab-to-AWS-CodeCommit push mirroring cannot use SSH authentication until [GitLab issue 34014](https://gitlab.com/gitlab-org/gitlab/-/issues/34014) is resolved. @@ -214,10 +218,9 @@ If it isn't working correctly, a red `error` tag appears and shows the error mes You can set up a repository to automatically have its branches, tags, and commits updated from an upstream repository. -This is useful when a repository you're interested in is located on a different server, and you want -to be able to browse its content and its activity using the familiar GitLab interface. - -To configure mirror pulling for an existing project: +If a repository you're interested in is located on a different server, and you want +to browse its content and its activity using the GitLab interface, you can configure +mirror pulling: 1. If you [configured two-factor authentication (2FA)](https://docs.github.com/en/github/authenticating-to-github/securing-your-account-with-two-factor-authentication-2fa) for GitHub, create a [personal access token for GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) @@ -244,7 +247,7 @@ Because GitLab is now set to pull changes from the upstream repository, you shou directly to the repository on GitLab. Instead, any commits should be pushed to the remote repository. Changes pushed to the remote repository are pulled into the GitLab repository, either: -- Automatically within a certain period of time. +- Automatically in a certain period of time. - When a [forced update](#forcing-an-update) is initiated. WARNING: @@ -254,7 +257,7 @@ Deleted branches and tags in the upstream repository are not reflected in the Gi ### How it works -Once the pull mirroring feature has been enabled for a repository, the repository is added to a queue. +After the pull mirroring feature has been enabled for a repository, the repository is added to a queue. Once per minute, a Sidekiq cron job schedules repository mirrors to update, based on: @@ -556,7 +559,7 @@ Bidirectional mirroring should not be used as a permanent configuration. Refer t [Git Fusion](https://www.perforce.com/manuals/git-fusion/#Git-Fusion/section_avy_hyc_gl.html) provides a Git interface to [Perforce Helix](https://www.perforce.com/products) which can be used by GitLab to bidirectionally -mirror projects with GitLab. This may be useful in some situations when migrating from Perforce Helix +mirror projects with GitLab. This can help you in some situations when migrating from Perforce Helix to GitLab where overlapping Perforce Helix workspaces cannot be migrated simultaneously to GitLab. If using mirroring with Perforce Helix, you should only mirror protected branches. Perforce Helix diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 632717e1b73..b719b046977 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -599,6 +599,7 @@ module API :custom_attributes, :last_activity_after, :last_activity_before, + :topic, :repository_storage) .symbolize_keys .compact @@ -611,7 +612,6 @@ module API finder_params[:user] = params.delete(:user) if params[:user] finder_params[:id_after] = sanitize_id_param(params[:id_after]) if params[:id_after] finder_params[:id_before] = sanitize_id_param(params[:id_before]) if params[:id_before] - finder_params[:tag] = params[:topic] if params[:topic].present? finder_params end diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index c796b50f5f3..0c139f30a0b 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -778,40 +778,40 @@ module API ::Integrations::Assembla, ::Integrations::Bamboo, ::Integrations::Bugzilla, + ::Integrations::Buildkite, ::Integrations::Campfire, ::Integrations::Confluence, ::Integrations::CustomIssueTracker, ::Integrations::Datadog, + ::Integrations::DroneCi, ::Integrations::EmailsOnPush, ::Integrations::Ewm, ::Integrations::ExternalWiki, ::Integrations::Flowdock, ::Integrations::Irker, + ::Integrations::Jenkins, ::Integrations::Jira, ::Integrations::Packagist, ::Integrations::PipelinesEmail, ::Integrations::Pivotaltracker, ::Integrations::Redmine, + ::Integrations::Teamcity, ::Integrations::Youtrack, - ::BuildkiteService, ::DiscordService, - ::DroneCiService, ::HangoutsChatService, - ::JenkinsService, ::MattermostSlashCommandsService, ::SlackSlashCommandsService, ::PrometheusService, ::PushoverService, ::SlackService, ::MattermostService, - ::MicrosoftTeamsService, - ::TeamcityService + ::MicrosoftTeamsService ] end def self.development_service_classes [ - ::MockCiService, + ::Integrations::MockCi, ::MockMonitoringService ] end diff --git a/lib/gitlab/ci/build/auto_retry.rb b/lib/gitlab/ci/build/auto_retry.rb index e6ef12975c2..b98d1d7b330 100644 --- a/lib/gitlab/ci/build/auto_retry.rb +++ b/lib/gitlab/ci/build/auto_retry.rb @@ -7,6 +7,11 @@ class Gitlab::Ci::Build::AutoRetry scheduler_failure: 2 }.freeze + RETRY_OVERRIDES = { + ci_quota_exceeded: 0, + no_matching_runner: 0 + }.freeze + def initialize(build) @build = build end @@ -19,13 +24,18 @@ class Gitlab::Ci::Build::AutoRetry private + delegate :failure_reason, to: :@build + def within_max_retry_limit? max_allowed_retries > 0 && max_allowed_retries > @build.retries_count end def max_allowed_retries strong_memoize(:max_allowed_retries) do - options_retry_max || DEFAULT_RETRIES.fetch(@build.failure_reason.to_sym, 0) + RETRY_OVERRIDES[failure_reason.to_sym] || + options_retry_max || + DEFAULT_RETRIES[failure_reason.to_sym] || + 0 end end @@ -38,7 +48,7 @@ class Gitlab::Ci::Build::AutoRetry end def retry_on_reason_or_always? - options_retry_when.include?(@build.failure_reason.to_s) || + options_retry_when.include?(failure_reason.to_s) || options_retry_when.include?('always') end diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml index 693cf1469c2..60581d2a31b 100644 --- a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml @@ -46,10 +46,13 @@ dast: $REVIEW_DISABLED && $DAST_WEBSITE == null && $DAST_API_SPECIFICATION == null when: never - - if: $CI_COMMIT_BRANCH && + - if: $CI_MERGE_REQUEST_IID && $CI_KUBERNETES_ACTIVE && $GITLAB_FEATURES =~ /\bdast\b/ + - if: $CI_MERGE_REQUEST_IID && ($DAST_WEBSITE || $DAST_API_SPECIFICATION) + - if: $CI_OPEN_MERGE_REQUESTS + when: never - if: $CI_COMMIT_BRANCH && - $DAST_WEBSITE - - if: $CI_COMMIT_BRANCH && - $DAST_API_SPECIFICATION + $CI_KUBERNETES_ACTIVE && + $GITLAB_FEATURES =~ /\bdast\b/ + - if: $CI_COMMIT_BRANCH && ($DAST_WEBSITE || $DAST_API_SPECIFICATION) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 71db8ab6067..8139a294269 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -20,7 +20,7 @@ module Gitlab raise UnknownIncomingEmail unless handler handler.execute.tap do - Gitlab::Metrics.add_event(handler.metrics_event, handler.metrics_params) + Gitlab::Metrics::BackgroundTransaction.current&.add_event(handler.metrics_event, handler.metrics_params) end end diff --git a/lib/gitlab/integrations/sti_type.rb b/lib/gitlab/integrations/sti_type.rb index 5a38c382b81..f043a8fa060 100644 --- a/lib/gitlab/integrations/sti_type.rb +++ b/lib/gitlab/integrations/sti_type.rb @@ -4,9 +4,9 @@ module Gitlab module Integrations class StiType < ActiveRecord::Type::String NAMESPACED_INTEGRATIONS = Set.new(%w( - Asana Assembla Bamboo Bugzilla Campfire Confluence CustomIssueTracker Datadog - EmailsOnPush Ewm ExternalWiki Flowdock IssueTracker Irker Jira Packagist PipelinesEmail - Pivotaltracker Redmine Youtrack + Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog + DroneCi EmailsOnPush Ewm ExternalWiki Flowdock IssueTracker Irker Jenkins Jira MockCi Packagist + PipelinesEmail Pivotaltracker Redmine Teamcity Youtrack )).freeze def cast(value) diff --git a/lib/gitlab/usage/metrics/aggregates/aggregate.rb b/lib/gitlab/usage/metrics/aggregates/aggregate.rb index 4c40bfbc06f..71a14e02f9d 100644 --- a/lib/gitlab/usage/metrics/aggregates/aggregate.rb +++ b/lib/gitlab/usage/metrics/aggregates/aggregate.rb @@ -22,9 +22,7 @@ module Gitlab }.freeze class Aggregate - delegate :weekly_time_range, - :monthly_time_range, - to: Gitlab::UsageDataCounters::HLLRedisCounter + include Gitlab::Usage::TimeFrame def initialize(recorded_at) @aggregated_metrics = load_metrics(AGGREGATED_METRICS_PATH) @@ -32,15 +30,15 @@ module Gitlab end def all_time_data - aggregated_metrics_data(start_date: nil, end_date: nil, time_frame: Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME) + aggregated_metrics_data(start_date: nil, end_date: nil, time_frame: Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME) end def monthly_data - aggregated_metrics_data(**monthly_time_range.merge(time_frame: Gitlab::Utils::UsageData::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME)) + aggregated_metrics_data(**monthly_time_range.merge(time_frame: Gitlab::Usage::TimeFrame::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME)) end def weekly_data - aggregated_metrics_data(**weekly_time_range.merge(time_frame: Gitlab::Utils::UsageData::SEVEN_DAYS_TIME_FRAME_NAME)) + aggregated_metrics_data(**weekly_time_range.merge(time_frame: Gitlab::Usage::TimeFrame::SEVEN_DAYS_TIME_FRAME_NAME)) end private @@ -54,7 +52,7 @@ module Gitlab case aggregation[:source] when REDIS_SOURCE - if time_frame == Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME + if time_frame == Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME data[aggregation[:name]] = Gitlab::Utils::UsageData::FALLBACK Gitlab::ErrorTracking .track_and_raise_for_dev_exception( diff --git a/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll.rb b/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll.rb index 3069afab147..eccf79b9703 100644 --- a/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll.rb +++ b/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll.rb @@ -56,15 +56,15 @@ module Gitlab end def time_period_to_human_name(time_period) - return Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME if time_period.blank? + return Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME if time_period.blank? start_date = time_period.first.to_date end_date = time_period.last.to_date if (end_date - start_date).to_i > 7 - Gitlab::Utils::UsageData::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME + Gitlab::Usage::TimeFrame::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME else - Gitlab::Utils::UsageData::SEVEN_DAYS_TIME_FRAME_NAME + Gitlab::Usage::TimeFrame::SEVEN_DAYS_TIME_FRAME_NAME end end end diff --git a/lib/gitlab/usage/metrics/instrumentations/base_metric.rb b/lib/gitlab/usage/metrics/instrumentations/base_metric.rb index 88e520abce8..7b5bee3f8bd 100644 --- a/lib/gitlab/usage/metrics/instrumentations/base_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/base_metric.rb @@ -6,6 +6,7 @@ module Gitlab module Instrumentations class BaseMetric include Gitlab::Utils::UsageData + include Gitlab::Usage::TimeFrame attr_reader :time_frame attr_reader :options diff --git a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb index f83f90dea03..24994f14167 100644 --- a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb @@ -52,7 +52,7 @@ module Gitlab def time_constraints case time_frame when '28d' - { created_at: 30.days.ago..2.days.ago } + monthly_time_range_db_params when 'all' {} when 'none' diff --git a/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb index e6d892f217b..502a8147473 100644 --- a/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb @@ -35,9 +35,9 @@ module Gitlab def time_constraints case time_frame when '28d' - { start_date: 4.weeks.ago.to_date, end_date: Date.current } + monthly_time_range when '7d' - { start_date: 7.days.ago.to_date, end_date: Date.current } + weekly_time_range else raise "Unknown time frame: #{time_frame} for RedisHLLMetric" end diff --git a/lib/gitlab/usage/time_frame.rb b/lib/gitlab/usage/time_frame.rb new file mode 100644 index 00000000000..966a087ee07 --- /dev/null +++ b/lib/gitlab/usage/time_frame.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Gitlab + module Usage + module TimeFrame + ALL_TIME_TIME_FRAME_NAME = "all" + SEVEN_DAYS_TIME_FRAME_NAME = "7d" + TWENTY_EIGHT_DAYS_TIME_FRAME_NAME = "28d" + + def weekly_time_range + { start_date: 7.days.ago.to_date, end_date: Date.current } + end + + def monthly_time_range + { start_date: 4.weeks.ago.to_date, end_date: Date.current } + end + + # This time range is skewed for batch counter performance. + # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42972 + def monthly_time_range_db_params(column: :created_at) + { column => 30.days.ago..2.days.ago } + end + end + end +end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index aeae176e943..6048a3b2283 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -33,6 +33,7 @@ module Gitlab class << self include Gitlab::Utils::UsageData include Gitlab::Utils::StrongMemoize + include Gitlab::Usage::TimeFrame def data(force_refresh: false) Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) do @@ -55,7 +56,7 @@ module Gitlab .merge(object_store_usage_data) .merge(topology_usage_data) .merge(usage_activity_by_stage) - .merge(usage_activity_by_stage(:usage_activity_by_stage_monthly, last_28_days_time_period)) + .merge(usage_activity_by_stage(:usage_activity_by_stage_monthly, monthly_time_range_db_params)) .merge(analytics_unique_visits_data) .merge(compliance_unique_visits_data) .merge(search_unique_visits_data) @@ -228,17 +229,17 @@ module Gitlab { counts_monthly: { # rubocop: disable UsageData/LargeTable: - deployments: deployment_count(Deployment.where(last_28_days_time_period)), - successful_deployments: deployment_count(Deployment.success.where(last_28_days_time_period)), - failed_deployments: deployment_count(Deployment.failed.where(last_28_days_time_period)), + deployments: deployment_count(Deployment.where(monthly_time_range_db_params)), + successful_deployments: deployment_count(Deployment.success.where(monthly_time_range_db_params)), + failed_deployments: deployment_count(Deployment.failed.where(monthly_time_range_db_params)), # rubocop: enable UsageData/LargeTable: - projects: count(Project.where(last_28_days_time_period), start: minimum_id(Project), finish: maximum_id(Project)), - packages: count(::Packages::Package.where(last_28_days_time_period)), - personal_snippets: count(PersonalSnippet.where(last_28_days_time_period)), - project_snippets: count(ProjectSnippet.where(last_28_days_time_period)), - projects_with_alerts_created: distinct_count(::AlertManagement::Alert.where(last_28_days_time_period), :project_id) + projects: count(Project.where(monthly_time_range_db_params), start: minimum_id(Project), finish: maximum_id(Project)), + packages: count(::Packages::Package.where(monthly_time_range_db_params)), + personal_snippets: count(PersonalSnippet.where(monthly_time_range_db_params)), + project_snippets: count(ProjectSnippet.where(monthly_time_range_db_params)), + projects_with_alerts_created: distinct_count(::AlertManagement::Alert.where(monthly_time_range_db_params), :project_id) }.merge( - snowplow_event_counts(last_28_days_time_period(column: :collector_tstamp)) + snowplow_event_counts(monthly_time_range_db_params(column: :collector_tstamp)) ).tap do |data| data[:snippets] = add(data[:personal_snippets], data[:project_snippets]) end @@ -522,10 +523,6 @@ module Gitlab "#{platform}-#{ohai_data['platform_version']}" end - def last_28_days_time_period(column: :created_at) - { column => 30.days.ago..2.days.ago } - end - # Source: https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/ping_metrics_to_stage_mapping_data.csv def usage_activity_by_stage(key = :usage_activity_by_stage, time_period = {}) { @@ -743,7 +740,7 @@ module Gitlab hash[target] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target) } end results['analytics_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) } - results['analytics_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics, start_date: 4.weeks.ago.to_date, end_date: Date.current) } + results['analytics_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics, **monthly_time_range) } { analytics_unique_visits: results } end @@ -753,7 +750,7 @@ module Gitlab hash[target] = redis_usage_data { unique_visit_service.unique_visits_for(targets: target) } end results['compliance_unique_visits_for_any_target'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance) } - results['compliance_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance, start_date: 4.weeks.ago.to_date, end_date: Date.current) } + results['compliance_unique_visits_for_any_target_monthly'] = redis_usage_data { unique_visit_service.unique_visits_for(targets: :compliance, **monthly_time_range) } { compliance_unique_visits: results } end @@ -761,11 +758,11 @@ module Gitlab def search_unique_visits_data events = ::Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category('search') results = events.each_with_object({}) do |event, hash| - hash[event] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: event, start_date: 7.days.ago.to_date, end_date: Date.current) } + hash[event] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: event, **weekly_time_range) } end - results['search_unique_visits_for_any_target_weekly'] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: 7.days.ago.to_date, end_date: Date.current) } - results['search_unique_visits_for_any_target_monthly'] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: 4.weeks.ago.to_date, end_date: Date.current) } + results['search_unique_visits_for_any_target_weekly'] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, **weekly_time_range) } + results['search_unique_visits_for_any_target_monthly'] = redis_usage_data { ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, **monthly_time_range) } { search_unique_visits: results } end diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb index 833eebd5d04..2a231f8fce0 100644 --- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb +++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb @@ -38,6 +38,7 @@ module Gitlab # * Get unique counts per user: Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: 'g_compliance_dashboard', start_date: 28.days.ago, end_date: Date.current) class << self include Gitlab::Utils::UsageData + include Gitlab::Usage::TimeFrame # Track unique events # @@ -98,14 +99,6 @@ module Gitlab end end - def weekly_time_range - { start_date: 7.days.ago.to_date, end_date: Date.current } - end - - def monthly_time_range - { start_date: 4.weeks.ago.to_date, end_date: Date.current } - end - def known_event?(event_name) event_for(event_name).present? end diff --git a/lib/gitlab/utils/usage_data.rb b/lib/gitlab/utils/usage_data.rb index a90f617393b..4ea5b5a87de 100644 --- a/lib/gitlab/utils/usage_data.rb +++ b/lib/gitlab/utils/usage_data.rb @@ -42,9 +42,6 @@ module Gitlab FALLBACK = -1 HISTOGRAM_FALLBACK = { '-1' => -1 }.freeze DISTRIBUTED_HLL_FALLBACK = -2 - ALL_TIME_TIME_FRAME_NAME = "all" - SEVEN_DAYS_TIME_FRAME_NAME = "7d" - TWENTY_EIGHT_DAYS_TIME_FRAME_NAME = "28d" MAX_BUCKET_SIZE = 100 def count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil) diff --git a/lib/tasks/gitlab/docs/redirect.rake b/lib/tasks/gitlab/docs/redirect.rake index 0c8e0755348..bb5dad94a8f 100644 --- a/lib/tasks/gitlab/docs/redirect.rake +++ b/lib/tasks/gitlab/docs/redirect.rake @@ -1,8 +1,11 @@ # frozen_string_literal: true require 'date' require 'pathname' +require "yaml" +# # https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page +# namespace :gitlab do namespace :docs do desc 'GitLab | Docs | Create a doc redirect' @@ -38,13 +41,14 @@ namespace :gitlab do # - If this is an external URL, move the date 1 year later. # - If this is a relative URL, move the date 3 months later. # - date = Time.now.utc.strftime('%Y-%m-%d') - date = new_path.start_with?('http') ? Date.parse(date) >> 12 : Date.parse(date) >> 3 + today = Time.now.utc.to_date + date = new_path.start_with?('http') ? today >> 12 : today >> 3 puts "=> Creating new redirect from #{old_path} to #{new_path}" File.open(old_path, 'w') do |post| post.puts '---' post.puts "redirect_to: '#{new_path}'" + post.puts "remove_date: '#{date}'" post.puts '---' post.puts post.puts "This file was moved to [another location](#{new_path})." @@ -53,5 +57,69 @@ namespace :gitlab do post.puts "<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->" end end + + desc 'GitLab | Docs | Clean up old redirects' + task :clean_redirects do + # + # Calculate new path from the redirect URL. + # + # If the redirect is not a full URL: + # 1. Create a new Pathname of the file + # 2. Use dirname to get all but the last component of the path + # 3. Join with the redirect_to entry + # 4. Substitute: + # - '.md' => '.html' + # - 'doc/' => '/ee/' + # + # If the redirect URL is a full URL pointing to the Docs site + # (cross-linking among the 4 products), remove the FQDN prefix: + # + # From : https://docs.gitlab.com/ee/install/requirements.html + # To : /ee/install/requirements.html + # + def new_path(redirect, filename) + if !redirect.start_with?('http') + Pathname.new(filename).dirname.join(redirect).to_s.gsub(%r(\.md), '.html').gsub(%r(doc/), '/ee/') + elsif redirect.start_with?('https://docs.gitlab.com') + redirect.gsub('https://docs.gitlab.com', '') + else + redirect + end + end + + today = Time.now.utc.to_date + + # + # Find the files to be deleted. + # Exclude 'doc/development/documentation/index.md' because it + # contains an example of the YAML front matter. + # + files_to_be_deleted = `grep -Ir 'remove_date:' doc | grep -v doc/development/documentation/index.md | cut -d ":" -f 1`.split("\n") + + # + # Iterate over the files to be deleted and print the needed + # YAML entries for the Docs site redirects. + # + files_to_be_deleted.each do |filename| + frontmatter = YAML.safe_load(File.read(filename)) + remove_date = Date.parse(frontmatter['remove_date']) + old_path = filename.gsub(%r(\.md), '.html').gsub(%r(doc/), '/ee/') + + # + # Check if the removal date is before today, and delete the file and + # print the content to be pasted in + # https://gitlab.com/gitlab-org/gitlab-docs/-/blob/master/content/_data/redirects.yaml. + # The remove_date of redirects.yaml should be nine months in the future. + # To not be confused with the remove_date of the Markdown page. + # + if remove_date < today + File.delete(filename) if File.exist?(filename) + + puts " - from: #{old_path}" + puts " to: #{new_path(frontmatter['redirect_to'], filename)}" + puts " remove_date: #{remove_date >> 9}" + end + end + end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 476cf5abf55..2b0403eb83f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -31147,6 +31147,9 @@ msgstr "" msgid "StatusCheck|Target branch" msgstr "" +msgid "StatusCheck|Update status check" +msgstr "" + msgid "StatusCheck|You are about to remove the %{name} status check." msgstr "" diff --git a/spec/config/metrics/aggregates/aggregated_metrics_spec.rb b/spec/config/metrics/aggregates/aggregated_metrics_spec.rb index 9aba86cdaf2..b5f8d363d40 100644 --- a/spec/config/metrics/aggregates/aggregated_metrics_spec.rb +++ b/spec/config/metrics/aggregates/aggregated_metrics_spec.rb @@ -25,9 +25,9 @@ RSpec.describe 'aggregated metrics' do RSpec::Matchers.define :have_known_time_frame do allowed_time_frames = [ - Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME, - Gitlab::Utils::UsageData::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME, - Gitlab::Utils::UsageData::SEVEN_DAYS_TIME_FRAME_NAME + Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME, + Gitlab::Usage::TimeFrame::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME, + Gitlab::Usage::TimeFrame::SEVEN_DAYS_TIME_FRAME_NAME ] match do |aggregate| @@ -63,7 +63,7 @@ RSpec.describe 'aggregated metrics' do let_it_be(:events_records) { known_events.select { |event| aggregate[:events].include?(event[:name]) } } it "does not include 'all' time frame for Redis sourced aggregate" do - expect(aggregate[:time_frame]).not_to include(Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME) + expect(aggregate[:time_frame]).not_to include(Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME) end it "only refers to known events" do diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index d8fb3b226ed..642c76abe79 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -95,7 +95,7 @@ RSpec.describe Projects::ServicesController do expect(response).to be_successful expect(json_response).to be_empty - expect(BuildkiteService.first).to be_present + expect(Integrations::Buildkite.first).to be_present end it 'creates the ServiceHook object' do @@ -103,7 +103,7 @@ RSpec.describe Projects::ServicesController do expect(response).to be_successful expect(json_response).to be_empty - expect(BuildkiteService.first.service_hook).to be_present + expect(Integrations::Buildkite.first.service_hook).to be_present end def do_put diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index 8b8a950d081..77f73fa4d76 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -38,7 +38,7 @@ FactoryBot.define do end end - factory :drone_ci_service do + factory :drone_ci_service, class: 'Integrations::DroneCi' do project active { true } drone_url { 'https://bamboo.example.com' } diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index d9b5ec17a4a..2ff6288e2e4 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -104,7 +104,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do before do create(:service, project: project, active: true, - type: 'CiService', + type: 'DroneCiService', category: 'ci') visit project_merge_request_path(project, merge_request) diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb index 53994ec018e..a5217007f3c 100644 --- a/spec/features/projects/branches/user_deletes_branch_spec.rb +++ b/spec/features/projects/branches/user_deletes_branch_spec.rb @@ -21,7 +21,7 @@ RSpec.describe "User deletes branch", :js do branch_search.native.send_keys(:enter) page.within(".js-branch-improve\\/awesome") do - accept_alert { find(".btn-danger").click } + accept_alert { click_link(title: 'Delete branch') } end wait_for_requests diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index f805416b03d..0c89ee28f30 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -99,11 +99,13 @@ RSpec.describe 'Branches' do end describe 'Delete unprotected branch on Overview' do - it 'removes branch after confirmation', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/239019' do + it 'removes branch after confirmation', :js do visit project_branches_filtered_path(project, state: 'all') expect(all('.all-branches').last).to have_selector('li', count: 20) - accept_confirm { first('.js-branch-item .btn-danger').click } + accept_confirm do + within('.js-branch-item', match: :first) { click_link(title: 'Delete branch') } + end expect(all('.all-branches').last).to have_selector('li', count: 19) end @@ -172,7 +174,9 @@ RSpec.describe 'Branches' do expect(page).to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 1) - accept_confirm { find('.js-branch-fix .btn-danger').click } + accept_confirm do + within('.js-branch-fix') { click_link(title: 'Delete branch') } + end expect(page).not_to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 0) diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 1d7be7fa7a3..bc2ea540879 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -333,7 +333,7 @@ RSpec.describe 'Environment' do visit project_branches_filtered_path(project, state: 'all', search: 'feature') remove_branch_with_hooks(project, user, 'feature') do - page.within('.js-branch-feature') { find('a.btn-danger').click } + within('.js-branch-feature') { click_link(title: 'Delete branch') } end visit_environment(environment) diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb index d9e200cf563..c6413685f38 100644 --- a/spec/features/projects/services/disable_triggers_spec.rb +++ b/spec/features/projects/services/disable_triggers_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Disable individual triggers', :js do let(:service_name) { 'Jenkins' } it 'shows trigger checkboxes' do - event_count = JenkinsService.supported_events.count + event_count = Integrations::Jenkins.supported_events.count expect(page).to have_content "Trigger" expect(page).to have_css(checkbox_selector, visible: :all, count: event_count) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index c18b0f2688b..2ac829d406c 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -128,23 +128,23 @@ RSpec.describe 'Project' do end it 'shows project topics' do - project.update_attribute(:tag_list, 'topic1') + project.update_attribute(:topic_list, 'topic1') visit path expect(page).to have_css('.home-panel-topic-list') - expect(page).to have_link('Topic1', href: explore_projects_path(tag: 'topic1')) + expect(page).to have_link('Topic1', href: explore_projects_path(topic: 'topic1')) end - it 'shows up to 3 project tags' do - project.update_attribute(:tag_list, 'topic1, topic2, topic3, topic4') + it 'shows up to 3 project topics' do + project.update_attribute(:topic_list, 'topic1, topic2, topic3, topic4') visit path expect(page).to have_css('.home-panel-topic-list') - expect(page).to have_link('Topic1', href: explore_projects_path(tag: 'topic1')) - expect(page).to have_link('Topic2', href: explore_projects_path(tag: 'topic2')) - expect(page).to have_link('Topic3', href: explore_projects_path(tag: 'topic3')) + expect(page).to have_link('Topic1', href: explore_projects_path(topic: 'topic1')) + expect(page).to have_link('Topic2', href: explore_projects_path(topic: 'topic2')) + expect(page).to have_link('Topic3', href: explore_projects_path(topic: 'topic3')) expect(page).to have_content('+ 1 more') end end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 207b74c990a..2436132297e 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -21,13 +21,13 @@ RSpec.describe 'Protected Branches', :js do expect(ProtectedBranch.count).to eq(1) end - it 'does not allow developer to removes protected branch' do + it 'does not allow developer to remove protected branch' do visit project_branches_path(project) find('input[data-testid="branch-search"]').set('fix') find('input[data-testid="branch-search"]').native.send_keys(:enter) - expect(page).to have_css('.btn-danger.disabled') + expect(page).to have_selector('button[data-testid="remove-protected-branch"][disabled]') end end end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 364e5de4ece..f47f070fea8 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -137,9 +137,9 @@ RSpec.describe ProjectsFinder do end end - describe 'filter by tags' do + describe 'filter by tags (deprecated)' do before do - public_project.tag_list = 'foo' + public_project.topic_list = 'foo' public_project.save! end @@ -148,6 +148,17 @@ RSpec.describe ProjectsFinder do it { is_expected.to eq([public_project]) } end + describe 'filter by topics' do + before do + public_project.topic_list = 'foo' + public_project.save! + end + + let(:params) { { topic: 'foo' } } + + it { is_expected.to eq([public_project]) } + end + describe 'filter by personal' do let!(:personal_project) { create(:project, namespace: user.namespace) } let(:params) { { personal: true } } diff --git a/spec/lib/gitlab/ci/build/auto_retry_spec.rb b/spec/lib/gitlab/ci/build/auto_retry_spec.rb index cfa8c9cd938..b107553bbce 100644 --- a/spec/lib/gitlab/ci/build/auto_retry_spec.rb +++ b/spec/lib/gitlab/ci/build/auto_retry_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Build::AutoRetry do describe '#allowed?' do using RSpec::Parameterized::TableSyntax - let(:build) { create(:ci_build) } + let(:build) { build_stubbed(:ci_build) } subject { auto_retry.allowed? } @@ -22,6 +22,8 @@ RSpec.describe Gitlab::Ci::Build::AutoRetry do "not matching reason" | 0 | { when: %w[script_error], max: 2 } | :api_failure | false "scheduler failure override" | 1 | { when: %w[scheduler_failure], max: 1 } | :scheduler_failure | false "default for scheduler failure" | 1 | {} | :scheduler_failure | true + "quota is exceeded" | 0 | { max: 2 } | :ci_quota_exceeded | false + "no matching runner" | 0 | { max: 2 } | :no_matching_runner | false end with_them do diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb index 324ed498abc..cdcc862c376 100644 --- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb +++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do end context 'when models using single-type inheritance are used' do - let(:models) { [Group, CiService, Namespace] } + let(:models) { [Group, Integrations::BaseCi, Namespace] } before do models.each do |model| diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 9b05c12ef57..2c1fe529a5d 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -5,9 +5,13 @@ require 'spec_helper' RSpec.describe Gitlab::Email::Receiver do include_context :email_shared_context - shared_examples 'correctly finds the mail key' do - specify do + shared_examples 'correctly finds the mail key and adds metric event' do + let(:metric_transaction) { double('Gitlab::Metrics::WebTransaction') } + + specify :aggregate_failures do expect(Gitlab::Email::Handler).to receive(:for).with(an_instance_of(Mail::Message), 'gitlabhq/gitlabhq+auth_token').and_return(handler) + expect(::Gitlab::Metrics::BackgroundTransaction).to receive(:current).and_return(metric_transaction) + expect(metric_transaction).to receive(:add_event).with(handler.metrics_event, handler.metrics_params) receiver.execute end @@ -30,7 +34,7 @@ RSpec.describe Gitlab::Email::Receiver do context 'when in a Delivered-To header' do let(:email_raw) { fixture_file('emails/forwarded_new_issue.eml') } - it_behaves_like 'correctly finds the mail key' + it_behaves_like 'correctly finds the mail key and adds metric event' it 'parses the metadata' do expect(metadata[:delivered_to]). to eq(["incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com", "support@example.com"]) @@ -40,7 +44,7 @@ RSpec.describe Gitlab::Email::Receiver do context 'when in an Envelope-To header' do let(:email_raw) { fixture_file('emails/envelope_to_header.eml') } - it_behaves_like 'correctly finds the mail key' + it_behaves_like 'correctly finds the mail key and adds metric event' it 'parses the metadata' do expect(metadata[:envelope_to]). to eq(["incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com"]) @@ -50,7 +54,7 @@ RSpec.describe Gitlab::Email::Receiver do context 'when in an X-Envelope-To header' do let(:email_raw) { fixture_file('emails/x_envelope_to_header.eml') } - it_behaves_like 'correctly finds the mail key' + it_behaves_like 'correctly finds the mail key and adds metric event' it 'parses the metadata' do expect(metadata[:x_envelope_to]). to eq(["incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com"]) @@ -60,7 +64,7 @@ RSpec.describe Gitlab::Email::Receiver do context 'when enclosed with angle brackets in an Envelope-To header' do let(:email_raw) { fixture_file('emails/envelope_to_header_with_angle_brackets.eml') } - it_behaves_like 'correctly finds the mail key' + it_behaves_like 'correctly finds the mail key and adds metric event' end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index d2d9282e90b..5ddbc98a172 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -91,7 +91,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do expect(described_class.usage_activity_by_stage_package({})).to eq( projects_with_packages: 2 ) - expect(described_class.usage_activity_by_stage_package(described_class.last_28_days_time_period)).to eq( + expect(described_class.usage_activity_by_stage_package(described_class.monthly_time_range_db_params)).to eq( projects_with_packages: 1 ) end @@ -135,7 +135,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do project_clusters_disabled: 2, project_clusters_enabled: 10 ) - expect(described_class.usage_activity_by_stage_configure(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_configure(described_class.monthly_time_range_db_params)).to include( clusters_applications_cert_managers: 1, clusters_applications_helm: 1, clusters_applications_ingress: 1, @@ -185,7 +185,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do snippets: 2, suggestions: 2 ) - expect(described_class.usage_activity_by_stage_create(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_create(described_class.monthly_time_range_db_params)).to include( deploy_keys: 1, keys: 1, merge_requests: 1, @@ -225,7 +225,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do omniauth_providers: ['google_oauth2'], user_auth_by_provider: { 'group_saml' => 2, 'ldap' => 4, 'standard' => 0, 'two-factor' => 0, 'two-factor-via-u2f-device' => 0, "two-factor-via-webauthn-device" => 0 } ) - expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_manage(described_class.monthly_time_range_db_params)).to include( events: 1, groups: 1, users_created: 3, @@ -252,7 +252,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do unique_users_all_imports: 10 ) - expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_manage(described_class.monthly_time_range_db_params)).to include( unique_users_all_imports: 5 ) end @@ -327,7 +327,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do groups_imported: Gitlab::UsageData::DEPRECATED_VALUE } ) - expect(described_class.usage_activity_by_stage_manage(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_manage(described_class.monthly_time_range_db_params)).to include( { bulk_imports: { gitlab_v1: 1, @@ -411,7 +411,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do projects_with_enabled_alert_integrations_histogram: { '1' => 2 } ) - data_28_days = described_class.usage_activity_by_stage_monitor(described_class.last_28_days_time_period) + data_28_days = described_class.usage_activity_by_stage_monitor(described_class.monthly_time_range_db_params) expect(data_28_days).to include( clusters: 1, clusters_applications_prometheus: 1, @@ -450,7 +450,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do projects_jira_dvcs_cloud_active: 2, projects_jira_dvcs_server_active: 2 ) - expect(described_class.usage_activity_by_stage_plan(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_plan(described_class.monthly_time_range_db_params)).to include( issues: 2, notes: 1, projects: 1, @@ -479,7 +479,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do releases: 2, successful_deployments: 2 ) - expect(described_class.usage_activity_by_stage_release(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_release(described_class.monthly_time_range_db_params)).to include( deployments: 1, failed_deployments: 1, releases: 1, @@ -513,7 +513,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ci_triggers: 2, clusters_applications_runner: 2 ) - expect(described_class.usage_activity_by_stage_verify(described_class.last_28_days_time_period)).to include( + expect(described_class.usage_activity_by_stage_verify(described_class.monthly_time_range_db_params)).to include( ci_builds: 1, ci_external_pipelines: 1, ci_internal_pipelines: 1, diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 3b5f2a198e2..024ac1b8094 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -3162,6 +3162,81 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end + describe '#environments_in_self_and_descendants' do + subject { pipeline.environments_in_self_and_descendants } + + context 'when pipeline is not child nor parent' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + let_it_be(:build) { create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline) } + + it 'returns just the pipeline environment' do + expect(subject).to contain_exactly(build.deployment.environment) + end + end + + context 'when pipeline is in extended family' do + let_it_be(:parent) { create(:ci_pipeline) } + let_it_be(:parent_build) { create(:ci_build, :with_deployment, environment: 'staging', pipeline: parent) } + + let_it_be(:pipeline) { create(:ci_pipeline, child_of: parent) } + let_it_be(:build) { create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline) } + + let_it_be(:child) { create(:ci_pipeline, child_of: pipeline) } + let_it_be(:child_build) { create(:ci_build, :with_deployment, environment: 'canary', pipeline: child) } + + let_it_be(:grandchild) { create(:ci_pipeline, child_of: child) } + let_it_be(:grandchild_build) { create(:ci_build, :with_deployment, environment: 'test', pipeline: grandchild) } + + let_it_be(:sibling) { create(:ci_pipeline, child_of: parent) } + let_it_be(:sibling_build) { create(:ci_build, :with_deployment, environment: 'review', pipeline: sibling) } + + it 'returns its own environment and from all descendants' do + expected_environments = [ + build.deployment.environment, + child_build.deployment.environment, + grandchild_build.deployment.environment + ] + expect(subject).to match_array(expected_environments) + end + + it 'does not return parent environment' do + expect(subject).not_to include(parent_build.deployment.environment) + end + + it 'does not return sibling environment' do + expect(subject).not_to include(sibling_build.deployment.environment) + end + end + + context 'when each pipeline has multiple environments' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + let_it_be(:build1) { create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline) } + let_it_be(:build2) { create(:ci_build, :with_deployment, environment: 'staging', pipeline: pipeline) } + + let_it_be(:child) { create(:ci_pipeline, child_of: pipeline) } + let_it_be(:child_build1) { create(:ci_build, :with_deployment, environment: 'canary', pipeline: child) } + let_it_be(:child_build2) { create(:ci_build, :with_deployment, environment: 'test', pipeline: child) } + + it 'returns all related environments' do + expected_environments = [ + build1.deployment.environment, + build2.deployment.environment, + child_build1.deployment.environment, + child_build2.deployment.environment + ] + expect(subject).to match_array(expected_environments) + end + end + + context 'when pipeline has no environment' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + + it 'returns empty' do + expect(subject).to be_empty + end + end + end + describe '#root_ancestor' do subject { pipeline.root_ancestor } diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index 09a73a4cdcb..1b9b38a0932 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -245,6 +245,17 @@ RSpec.describe EnvironmentStatus do end end + context 'when there is a deployment in a child pipeline' do + let!(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) } + let!(:child_build) { create(:ci_build, :with_deployment, :start_review_app, pipeline: child_pipeline) } + let(:child_environment) { child_build.deployment.environment } + + it 'returns both parent and child entries' do + expect(subject.count).to eq(2) + expect(subject.map(&:id)).to contain_exactly(environment.id, child_environment.id) + end + end + context 'when environment is stopped' do before do environment.stop! diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/integrations/buildkite_spec.rb index f6bf1551bf0..7dc81da7003 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/integrations/buildkite_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BuildkiteService, :use_clean_rails_memory_store_caching do +RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers include StubRequests diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/integrations/drone_ci_spec.rb index 9aaf4f7a644..cae1cd189a9 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/integrations/drone_ci_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe DroneCiService, :use_clean_rails_memory_store_caching do +RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers describe 'associations' do @@ -32,7 +32,7 @@ RSpec.describe DroneCiService, :use_clean_rails_memory_store_caching do end shared_context :drone_ci_service do - let(:drone) { DroneCiService.new } + let(:drone) { described_class.new } let(:project) { create(:project, :repository, name: 'project') } let(:path) { project.full_path } let(:drone_url) { 'http://drone.example.com' } @@ -41,7 +41,7 @@ RSpec.describe DroneCiService, :use_clean_rails_memory_store_caching do let(:token) { 'secret' } let(:iid) { rand(1..9999) } - # URL's + # URLs let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" } let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" } diff --git a/spec/models/project_services/jenkins_service_spec.rb b/spec/models/integrations/jenkins_spec.rb index 4663e41736a..2374dfe4480 100644 --- a/spec/models/project_services/jenkins_service_spec.rb +++ b/spec/models/integrations/jenkins_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JenkinsService do +RSpec.describe Integrations::Jenkins do let(:project) { create(:project) } let(:jenkins_url) { 'http://jenkins.example.com/' } let(:jenkins_hook_url) { jenkins_url + 'project/my_project' } diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/integrations/teamcity_spec.rb index f71dad86a08..b88a4722ad4 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/integrations/teamcity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do +RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers include StubRequests diff --git a/spec/models/project_repository_storage_move_spec.rb b/spec/models/project_repository_storage_move_spec.rb deleted file mode 100644 index eb193a44680..00000000000 --- a/spec/models/project_repository_storage_move_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ProjectRepositoryStorageMove, type: :model do - let_it_be_with_refind(:project) { create(:project) } - - it_behaves_like 'handles repository moves' do - let(:container) { project } - let(:repository_storage_factory_key) { :project_repository_storage_move } - let(:error_key) { :project } - let(:repository_storage_worker) { Projects::UpdateRepositoryStorageWorker } - end - - describe 'state transitions' do - let(:storage) { 'test_second_storage' } - - before do - stub_storage_settings(storage => { 'path' => 'tmp/tests/extra_storage' }) - end - - context 'when started' do - subject(:storage_move) { create(:project_repository_storage_move, :started, container: project, destination_storage_name: storage) } - - context 'and transits to replicated' do - it 'sets the repository storage and marks the container as writable' do - storage_move.finish_replication! - - expect(project.repository_storage).to eq(storage) - expect(project).not_to be_repository_read_only - end - end - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6a0bc714731..ce22fc2b1f0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -6889,31 +6889,6 @@ RSpec.describe Project, factory_default: :keep do expect(project.tags.map(&:name)).to match_array(%w[topic1 topic2 topic3]) end end - - context 'intermediate state during background migration' do - before do - project.taggings.first.update!(context: 'tags') - project.instance_variable_set("@tag_list", nil) - project.reload - end - - it 'tag_list returns string array including old and new topics' do - expect(project.tag_list).to match_array(%w[topic1 topic2 topic3]) - end - - it 'tags returns old and new tag records' do - expect(project.tags.first.class.name).to eq('ActsAsTaggableOn::Tag') - expect(project.tags.map(&:name)).to match_array(%w[topic1 topic2 topic3]) - expect(project.taggings.map(&:context)).to match_array(%w[tags topics topics]) - end - - it 'update tag_list adds new topics and removes old topics' do - project.update!(tag_list: 'topic1, topic2, topic3, topic4') - - expect(project.tags.map(&:name)).to match_array(%w[topic1 topic2 topic3 topic4]) - expect(project.taggings.map(&:context)).to match_array(%w[topics topics topics topics]) - end - end end def finish_job(export_job) diff --git a/spec/models/snippet_repository_storage_move_spec.rb b/spec/models/snippet_repository_storage_move_spec.rb deleted file mode 100644 index f5ad837fb36..00000000000 --- a/spec/models/snippet_repository_storage_move_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SnippetRepositoryStorageMove, type: :model do - it_behaves_like 'handles repository moves' do - let_it_be_with_refind(:container) { create(:snippet) } - - let(:repository_storage_factory_key) { :snippet_repository_storage_move } - let(:error_key) { :snippet } - let(:repository_storage_worker) { Snippets::UpdateRepositoryStorageWorker } - end -end diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 76b77ee0de2..c64f9e8465f 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -12,7 +12,7 @@ RSpec.describe MergeRequestPresenter do context 'when no head pipeline' do it 'return status using CiService' do - ci_service = double(MockCiService) + ci_service = double(Integrations::MockCi) ci_status = double allow(resource.source_project) diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index cfee26a0d6a..bdfeb7a97f0 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -486,7 +486,7 @@ RSpec.shared_examples 'wiki controller actions' do end.not_to change { wiki.list_pages.size } expect(response).to render_template('shared/wikis/edit') - expect(assigns(:error).message).to eq('Could not delete wiki page') + expect(assigns(:error)).to eq('Could not delete wiki page') end end end diff --git a/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb b/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb deleted file mode 100644 index 256f665c0c4..00000000000 --- a/spec/workers/project_schedule_bulk_repository_shard_moves_worker_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ProjectScheduleBulkRepositoryShardMovesWorker do - it_behaves_like 'schedules bulk repository shard moves' do - let_it_be_with_reload(:container) { create(:project, :repository) } - - let(:move_service_klass) { Projects::RepositoryStorageMove } - let(:worker_klass) { Projects::UpdateRepositoryStorageWorker } - end -end diff --git a/spec/workers/project_update_repository_storage_worker_spec.rb b/spec/workers/project_update_repository_storage_worker_spec.rb deleted file mode 100644 index 6924e8a93a3..00000000000 --- a/spec/workers/project_update_repository_storage_worker_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ProjectUpdateRepositoryStorageWorker do - subject { described_class.new } - - it_behaves_like 'an update storage move worker' do - let_it_be_with_refind(:container) { create(:project, :repository) } - let_it_be(:repository_storage_move) { create(:project_repository_storage_move) } - - let(:service_klass) { Projects::UpdateRepositoryStorageService } - let(:repository_storage_move_klass) { Projects::RepositoryStorageMove } - end -end diff --git a/spec/workers/snippet_schedule_bulk_repository_shard_moves_worker_spec.rb b/spec/workers/snippet_schedule_bulk_repository_shard_moves_worker_spec.rb deleted file mode 100644 index a5f1c6b7b3d..00000000000 --- a/spec/workers/snippet_schedule_bulk_repository_shard_moves_worker_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SnippetScheduleBulkRepositoryShardMovesWorker do - it_behaves_like 'schedules bulk repository shard moves' do - let_it_be_with_reload(:container) { create(:snippet, :repository).tap { |snippet| snippet.create_repository } } - - let(:move_service_klass) { Snippets::RepositoryStorageMove } - let(:worker_klass) { Snippets::UpdateRepositoryStorageWorker } - end -end diff --git a/spec/workers/snippet_update_repository_storage_worker_spec.rb b/spec/workers/snippet_update_repository_storage_worker_spec.rb deleted file mode 100644 index 205cb2e432f..00000000000 --- a/spec/workers/snippet_update_repository_storage_worker_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SnippetUpdateRepositoryStorageWorker do - subject { described_class.new } - - it_behaves_like 'an update storage move worker' do - let_it_be_with_refind(:container) { create(:snippet, :repository) } - let_it_be(:repository_storage_move) { create(:snippet_repository_storage_move) } - - let(:service_klass) { Snippets::UpdateRepositoryStorageService } - let(:repository_storage_move_klass) { Snippets::RepositoryStorageMove } - end -end |