diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 085bf0f0a37..7521a6491af 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -460,6 +460,7 @@ img.emoji { .w-8em { width: 8em; } .w-3rem { width: 3rem; } .w-15p { width: 15%; } +.w-30p { width: 30%; } .w-70p { width: 70%; } .h-12em { height: 12em; } diff --git a/app/models/board.rb b/app/models/board.rb index f3f938224a4..38bbb550044 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -11,6 +11,8 @@ class Board < ApplicationRecord validates :group, presence: true, unless: :project scope :with_associations, -> { preload(:destroyable_lists) } + scope :order_by_name_asc, -> { order(arel_table[:name].lower.asc) } + scope :first_board, -> { where(id: self.order_by_name_asc.limit(1).select(:id)) } def project_needed? !group diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index bb3762c26f6..369a793f3d5 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -447,10 +447,6 @@ module Ci options_retry_when.include?('always') end - def latest? - !retried? - end - def any_unmet_prerequisites? prerequisites.present? end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 3943d991c87..7e3ba98d86c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -515,7 +515,9 @@ module Ci # rubocop: enable CodeReuse/ServiceClass def mark_as_processable_after_stage(stage_idx) - builds.skipped.after_stage(stage_idx).find_each(&:process) + builds.skipped.after_stage(stage_idx).find_each do |build| + Gitlab::OptimisticLocking.retry_lock(build, &:process) + end end def latest? @@ -554,6 +556,13 @@ module Ci end end + def needs_processing? + statuses + .where(processed: [false, nil]) + .latest + .exists? + end + # TODO: this logic is duplicate with Pipeline::Chain::Config::Content # we should persist this is `ci_pipelines.config_path` def config_path @@ -583,9 +592,8 @@ module Ci project.notes.for_commit_id(sha) end - def update_status + def set_status(new_status) retry_optimistic_lock(self) do - new_status = latest_builds_status.to_s case new_status when 'created' then nil when 'waiting_for_resource' then request_resource @@ -605,6 +613,10 @@ module Ci end end + def update_legacy_status + set_status(latest_builds_status.to_s) + end + def protected_ref? strong_memoize(:protected_ref) { project.protected_for?(git_ref) } end diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb index 9c56aa67e20..6c4b271cd2c 100644 --- a/app/models/ci/processable.rb +++ b/app/models/ci/processable.rb @@ -8,8 +8,26 @@ module Ci scope :preload_needs, -> { preload(:needs) } + def self.select_with_aggregated_needs(project) + return all unless Feature.enabled?(:ci_dag_support, project, default_enabled: true) + + aggregated_needs_names = Ci::BuildNeed + .scoped_build + .select("ARRAY_AGG(name)") + .to_sql + + all.select( + '*', + "(#{aggregated_needs_names}) as aggregated_needs_names" + ) + end + validates :type, presence: true + def aggregated_needs_names + read_attribute(:aggregated_needs_names) + end + def schedulable? raise NotImplementedError end diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 96041e02337..75f73429c2a 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -13,9 +13,12 @@ module Ci belongs_to :pipeline has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id + has_many :processables, class_name: 'Ci::Processable', foreign_key: :stage_id has_many :builds, foreign_key: :stage_id has_many :bridges, foreign_key: :stage_id + scope :ordered, -> { order(position: :asc) } + with_options unless: :importing? do validates :project, presence: true validates :pipeline, presence: true @@ -80,9 +83,8 @@ module Ci end end - def update_status + def set_status(new_status) retry_optimistic_lock(self) do - new_status = latest_stage_status.to_s case new_status when 'created' then nil when 'waiting_for_resource' then request_resource @@ -102,6 +104,10 @@ module Ci end end + def update_legacy_status + set_status(latest_stage_status.to_s) + end + def groups @groups ||= Ci::Group.fabricate(self) end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 773481da5f9..f9101609f89 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -40,6 +40,7 @@ class CommitStatus < ApplicationRecord scope :latest, -> { where(retried: [false, nil]) } scope :retried, -> { where(retried: true) } scope :ordered, -> { order(:name) } + scope :ordered_by_stage, -> { order(stage_idx: :asc) } scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) } scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) } scope :before_stage, -> (index) { where('stage_idx < ?', index) } @@ -57,6 +58,10 @@ class CommitStatus < ApplicationRecord preload(:project, :user) end + scope :with_project_preload, -> do + preload(project: :namespace) + end + scope :with_needs, -> (names = nil) do needs = Ci::BuildNeed.scoped_build.select(1) needs = needs.where(name: names) if names @@ -69,6 +74,15 @@ class CommitStatus < ApplicationRecord where('NOT EXISTS (?)', needs) end + scope :match_id_and_lock_version, -> (slice) do + # it expects that items are an array of attributes to match + # each hash needs to have `id` and `lock_version` + slice.inject(self) do |relation, item| + match = CommitStatus.where(item.slice(:id, :lock_version)) + relation.or(match) + end + end + # We use `CommitStatusEnums.failure_reasons` here so that EE can more easily # extend this `Hash` with new values. enum_with_nil failure_reason: ::CommitStatusEnums.failure_reasons @@ -86,6 +100,16 @@ class CommitStatus < ApplicationRecord # rubocop: enable CodeReuse/ServiceClass end + before_save if: :status_changed?, unless: :importing? do + if Feature.disabled?(:ci_atomic_processing, project) + self.processed = nil + elsif latest? + self.processed = false # force refresh of all dependent ones + elsif retried? + self.processed = true # retried are considered to be already processed + end + end + state_machine :status do event :process do transition [:skipped, :manual] => :created @@ -136,19 +160,13 @@ class CommitStatus < ApplicationRecord end after_transition do |commit_status, transition| - next unless commit_status.project next if transition.loopback? + next if commit_status.processed? + next unless commit_status.project commit_status.run_after_commit do - if pipeline_id - if complete? || manual? - PipelineProcessWorker.perform_async(pipeline_id, [id]) - else - PipelineUpdateWorker.perform_async(pipeline_id) - end - end + schedule_stage_and_pipeline_update - StageUpdateWorker.perform_async(stage_id) ExpireJobCacheWorker.perform_async(id) end end @@ -177,6 +195,11 @@ class CommitStatus < ApplicationRecord where(name: names).latest.slow_composite_status || 'success' end + def self.update_as_processed! + # Marks items as processed, and increases `lock_version` (Optimisitc Locking) + update_all('processed=TRUE, lock_version=COALESCE(lock_version,0)+1') + end + def locking_enabled? will_save_change_to_status? end @@ -193,6 +216,10 @@ class CommitStatus < ApplicationRecord calculate_duration end + def latest? + !retried? + end + def playable? false end @@ -244,4 +271,21 @@ class CommitStatus < ApplicationRecord v =~ /\d+/ ? v.to_i : v end end + + private + + def schedule_stage_and_pipeline_update + if Feature.enabled?(:ci_atomic_processing, project) + # Atomic Processing requires only single Worker + PipelineProcessWorker.perform_async(pipeline_id, [id]) + else + if complete? || manual? + PipelineProcessWorker.perform_async(pipeline_id, [id]) + else + PipelineUpdateWorker.perform_async(pipeline_id) + end + + StageUpdateWorker.perform_async(stage_id) + end + end end diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb index 6542112ba32..529af1277b0 100644 --- a/app/models/project_services/chat_message/base_message.rb +++ b/app/models/project_services/chat_message/base_message.rb @@ -4,6 +4,8 @@ require 'slack-notifier' module ChatMessage class BaseMessage + RELATIVE_LINK_REGEX = /!\[[^\]]*\]\((\/uploads\/[^\)]*)\)/.freeze + attr_reader :markdown attr_reader :user_full_name attr_reader :user_name @@ -59,7 +61,11 @@ module ChatMessage end def format(string) - Slack::Notifier::LinkFormatter.format(string) + Slack::Notifier::LinkFormatter.format(format_relative_links(string)) + end + + def format_relative_links(string) + string.gsub(RELATIVE_LINK_REGEX, "#{project_url}\\1") end def attachment_color diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb index 44d5a21b15f..8258d5d07d3 100644 --- a/app/services/boards/list_service.rb +++ b/app/services/boards/list_service.rb @@ -4,13 +4,24 @@ module Boards class ListService < Boards::BaseService def execute create_board! if parent.boards.empty? - boards + + if parent.multiple_issue_boards_available? + boards + else + # When multiple issue boards are not available + # a user is only allowed to view the default shown board + first_board + end end private def boards - parent.boards + parent.boards.order_by_name_asc + end + + def first_board + parent.boards.first_board end def create_board! @@ -18,5 +29,3 @@ module Boards end end end - -Boards::ListService.prepend_if_ee('EE::Boards::ListService') diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb new file mode 100644 index 00000000000..1ed295f5950 --- /dev/null +++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Ci + module PipelineProcessing + class AtomicProcessingService + include Gitlab::Utils::StrongMemoize + include ExclusiveLeaseGuard + + attr_reader :pipeline + + DEFAULT_LEASE_TIMEOUT = 1.minute + BATCH_SIZE = 20 + + def initialize(pipeline) + @pipeline = pipeline + @collection = AtomicProcessingService::StatusCollection.new(pipeline) + end + + def execute + return unless pipeline.needs_processing? + + success = try_obtain_lease { process! } + + # re-schedule if we need further processing + if success && pipeline.needs_processing? + PipelineProcessWorker.perform_async(pipeline.id) + end + + success + end + + private + + def process! + update_stages! + update_pipeline! + update_statuses_processed! + + true + end + + def update_stages! + pipeline.stages.ordered.each(&method(:update_stage!)) + end + + def update_stage!(stage) + # Update processables for a given stage in bulk/slices + ids = @collection.created_processable_ids_for_stage_position(stage.position) + ids.in_groups_of(BATCH_SIZE, false, &method(:update_processables!)) + + status = @collection.status_for_stage_position(stage.position) + stage.set_status(status) + end + + def update_processables!(ids) + created_processables = pipeline.processables.for_ids(ids) + .with_project_preload + .created + .latest + .ordered_by_stage + .select_with_aggregated_needs(project) + + created_processables.each(&method(:update_processable!)) + end + + def update_pipeline! + pipeline.set_status(@collection.status_of_all) + end + + def update_statuses_processed! + processing = @collection.processing_processables + processing.each_slice(BATCH_SIZE) do |slice| + pipeline.statuses.match_id_and_lock_version(slice) + .update_as_processed! + end + end + + def update_processable!(processable) + status = processable_status(processable) + return unless HasStatus::COMPLETED_STATUSES.include?(status) + + # transition status if possible + Gitlab::OptimisticLocking.retry_lock(processable) do |subject| + Ci::ProcessBuildService.new(project, subject.user) + .execute(subject, status) + + # update internal representation of status + # to make the status change of processable + # to be taken into account during further processing + @collection.set_processable_status( + processable.id, processable.status, processable.lock_version) + end + end + + def processable_status(processable) + if needs_names = processable.aggregated_needs_names + # Processable uses DAG, get status of all dependent needs + @collection.status_for_names(needs_names) + else + # Processable uses Stages, get status of prior stage + @collection.status_for_prior_stage_position(processable.stage_idx.to_i) + end + end + + def project + pipeline.project + end + + def lease_key + "#{super}::pipeline_id:#{pipeline.id}" + end + + def lease_timeout + DEFAULT_LEASE_TIMEOUT + end + end + end +end diff --git a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb new file mode 100644 index 00000000000..42e38a5c80f --- /dev/null +++ b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module Ci + module PipelineProcessing + class AtomicProcessingService + class StatusCollection + include Gitlab::Utils::StrongMemoize + + attr_reader :pipeline + + # We use these columns to perform an efficient + # calculation of a status + STATUSES_COLUMNS = [ + :id, :name, :status, :allow_failure, + :stage_idx, :processed, :lock_version + ].freeze + + def initialize(pipeline) + @pipeline = pipeline + @stage_statuses = {} + @prior_stage_statuses = {} + end + + # This method updates internal status for given ID + def set_processable_status(id, status, lock_version) + processable = all_statuses_by_id[id] + return unless processable + + processable[:status] = status + processable[:lock_version] = lock_version + end + + # This methods gets composite status of all processables + def status_of_all + status_for_array(all_statuses) + end + + # This methods gets composite status for processables with given names + def status_for_names(names) + name_statuses = all_statuses_by_name.slice(*names) + + status_for_array(name_statuses.values) + end + + # This methods gets composite status for processables before given stage + def status_for_prior_stage_position(position) + strong_memoize("status_for_prior_stage_position_#{position}") do + stage_statuses = all_statuses_grouped_by_stage_position + .select { |stage_position, _| stage_position < position } + + status_for_array(stage_statuses.values.flatten) + end + end + + # This methods gets a list of processables for a given stage + def created_processable_ids_for_stage_position(current_position) + all_statuses_grouped_by_stage_position[current_position] + .to_a + .select { |processable| processable[:status] == 'created' } + .map { |processable| processable[:id] } + end + + # This methods gets composite status for processables at a given stage + def status_for_stage_position(current_position) + strong_memoize("status_for_stage_position_#{current_position}") do + stage_statuses = all_statuses_grouped_by_stage_position[current_position].to_a + + status_for_array(stage_statuses.flatten) + end + end + + # This method returns a list of all processable, that are to be processed + def processing_processables + all_statuses.lazy.reject { |status| status[:processed] } + end + + private + + def status_for_array(statuses) + result = Gitlab::Ci::Status::Composite + .new(statuses) + .status + result || 'success' + end + + def all_statuses_grouped_by_stage_position + strong_memoize(:all_statuses_by_order) do + all_statuses.group_by { |status| status[:stage_idx].to_i } + end + end + + def all_statuses_by_id + strong_memoize(:all_statuses_by_id) do + all_statuses.map do |row| + [row[:id], row] + end.to_h + end + end + + def all_statuses_by_name + strong_memoize(:statuses_by_name) do + all_statuses.map do |row| + [row[:name], row] + end.to_h + end + end + + # rubocop: disable CodeReuse/ActiveRecord + def all_statuses + # We fetch all relevant data in one go. + # + # This is more efficient than relying + # on PostgreSQL to calculate composite status + # for us + # + # Since we need to reprocess everything + # we can fetch all of them and do processing + # ourselves. + strong_memoize(:all_statuses) do + raw_statuses = pipeline + .statuses + .latest + .ordered_by_stage + .pluck(*STATUSES_COLUMNS) + + raw_statuses.map do |row| + STATUSES_COLUMNS.zip(row).to_h + end + end + end + # rubocop: enable CodeReuse/ActiveRecord + end + end + end +end diff --git a/app/services/ci/pipeline_processing/legacy_processing_service.rb b/app/services/ci/pipeline_processing/legacy_processing_service.rb index d7535a5f743..400dc9f0abb 100644 --- a/app/services/ci/pipeline_processing/legacy_processing_service.rb +++ b/app/services/ci/pipeline_processing/legacy_processing_service.rb @@ -18,7 +18,7 @@ module Ci # only when the another job has finished success = process_builds_with_needs(trigger_build_ids) || success - @pipeline.update_status + @pipeline.update_legacy_status success end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 3a7d6ad9c3d..1ecef256233 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -11,9 +11,15 @@ module Ci def execute(trigger_build_ids = nil) update_retried - Ci::PipelineProcessing::LegacyProcessingService - .new(pipeline) - .execute(trigger_build_ids) + if Feature.enabled?(:ci_atomic_processing, pipeline.project) + Ci::PipelineProcessing::AtomicProcessingService + .new(pipeline) + .execute + else + Ci::PipelineProcessing::LegacyProcessingService + .new(pipeline) + .execute(trigger_build_ids) + end end private diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb index 5abfbd26641..1f00d54b6a7 100644 --- a/app/services/ci/retry_build_service.rb +++ b/app/services/ci/retry_build_service.rb @@ -11,7 +11,7 @@ module Ci reprocess!(build).tap do |new_build| build.pipeline.mark_as_processable_after_stage(build.stage_idx) - new_build.enqueue! + Gitlab::OptimisticLocking.retry_lock(new_build, &:enqueue) MergeRequests::AddTodoWhenBuildFailsService .new(project, current_user) @@ -31,15 +31,17 @@ module Ci attributes.push([:user, current_user]) - build.retried = true - Ci::Build.transaction do # mark all other builds of that name as retried build.pipeline.builds.latest .where(name: build.name) - .update_all(retried: true) + .update_all(retried: true, processed: true) - create_build!(attributes) + create_build!(attributes).tap do + # mark existing object as retried/processed without a reload + build.retried = true + build.processed = true + end end end # rubocop: enable CodeReuse/ActiveRecord @@ -49,6 +51,7 @@ module Ci def create_build!(attributes) build = project.builds.new(Hash[attributes]) build.deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment.new(build).to_resource + build.retried = false build.save! build end diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb index 5b742461f7a..0321ea5a6ce 100644 --- a/app/workers/pipeline_update_worker.rb +++ b/app/workers/pipeline_update_worker.rb @@ -7,10 +7,7 @@ class PipelineUpdateWorker queue_namespace :pipeline_processing latency_sensitive_worker! - # rubocop: disable CodeReuse/ActiveRecord def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id) - .try(:update_status) + Ci::Pipeline.find_by_id(pipeline_id)&.update_legacy_status end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/workers/stage_update_worker.rb b/app/workers/stage_update_worker.rb index de2454128f6..a96c4c6dda2 100644 --- a/app/workers/stage_update_worker.rb +++ b/app/workers/stage_update_worker.rb @@ -7,11 +7,7 @@ class StageUpdateWorker queue_namespace :pipeline_processing latency_sensitive_worker! - # rubocop: disable CodeReuse/ActiveRecord def perform(stage_id) - Ci::Stage.find_by(id: stage_id).try do |stage| - stage.update_status - end + Ci::Stage.find_by_id(stage_id)&.update_legacy_status end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/changelogs/unreleased/36716-issue-reference-changes-when-adding-to-epic.yml b/changelogs/unreleased/36716-issue-reference-changes-when-adding-to-epic.yml new file mode 100644 index 00000000000..dc58c0e51ff --- /dev/null +++ b/changelogs/unreleased/36716-issue-reference-changes-when-adding-to-epic.yml @@ -0,0 +1,5 @@ +--- +title: Fix relative links in Slack message +merge_request: 22608 +author: +type: fixed diff --git a/changelogs/unreleased/bw-board-sorting.yml b/changelogs/unreleased/bw-board-sorting.yml new file mode 100644 index 00000000000..3eaabe67aa9 --- /dev/null +++ b/changelogs/unreleased/bw-board-sorting.yml @@ -0,0 +1,5 @@ +--- +title: Project issue board names now sorted correctly in FOSS +merge_request: 22807 +author: +type: fixed diff --git a/changelogs/unreleased/improve-pipeline-processing.yml b/changelogs/unreleased/improve-pipeline-processing.yml new file mode 100644 index 00000000000..8e93f2d2d4d --- /dev/null +++ b/changelogs/unreleased/improve-pipeline-processing.yml @@ -0,0 +1,5 @@ +--- +title: Implement Atomic Processing that updates status of builds, stages and pipelines in one go +merge_request: 20229 +author: +type: performance diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 4e9131c1a46..417a68d6ad7 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -57,7 +57,7 @@ class Gitlab::Seeder::Pipelines BUILDS.each { |opts| build_create!(pipeline, opts) } EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) } pipeline.update_duration - pipeline.update_status + pipeline.update_legacy_status end end diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index 4698a42c8b6..b2252d31cac 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -187,7 +187,7 @@ class Gitlab::Seeder::CycleAnalytics pipeline.builds.each(&:enqueue) # make sure all pipelines in pending state pipeline.builds.each(&:run!) - pipeline.update_status + pipeline.update_legacy_status end end @@ -208,7 +208,7 @@ class Gitlab::Seeder::CycleAnalytics job = merge_request.head_pipeline.builds.where.not(environment: nil).last job.success! - job.pipeline.update_status + job.pipeline.update_legacy_status end end end diff --git a/db/migrate/20191115114032_add_processed_to_ci_builds.rb b/db/migrate/20191115114032_add_processed_to_ci_builds.rb new file mode 100644 index 00000000000..f6f8f5e85d6 --- /dev/null +++ b/db/migrate/20191115114032_add_processed_to_ci_builds.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddProcessedToCiBuilds < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_column :ci_builds, :processed, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 2c00254cd04..ebe75bf5abc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -697,6 +697,7 @@ ActiveRecord::Schema.define(version: 2020_01_14_204949) do t.integer "upstream_pipeline_id" t.bigint "resource_group_id" t.datetime_with_timezone "waiting_for_resource_at" + t.boolean "processed" t.index ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)" t.index ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id" t.index ["commit_id", "artifacts_expire_at", "id"], name: "index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial", where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))" diff --git a/doc/api/README.md b/doc/api/README.md index fd1717cb67d..ef3b578f04e 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -410,7 +410,7 @@ This method is controlled by the following parameters: In the example below, we list 50 [projects](projects.md) per page, ordered by `id` ascending. ```bash -curl --request PUT --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects?pagination=keyset&per_page=50&order_by=id&sort=asc" +curl --request GET --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects?pagination=keyset&per_page=50&order_by=id&sort=asc" ``` The response header includes a link to the next page. For example: diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 610b21234b1..a3b9124a2ba 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -211,7 +211,7 @@ create the actual RDS instance. Now, it's time to create the database: -1. Select **Instances** from the left menu and click **Create database**. +1. Select **Databases** from the left menu and click **Create database**. 1. Select PostgreSQL and click **Next**. 1. Since this is a production server, let's choose "Production". Click **Next**. 1. Let's see the instance specifications: diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 0f0397ec13b..2acb79e3e22 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -240,6 +240,7 @@ excluded_attributes: - :upstream_pipeline_id - :resource_group_id - :waiting_for_resource_at + - :processed sentry_issue: - :issue_id push_event_payload: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 06b9faec354..fc51ef694be 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -587,7 +587,9 @@ msgstr[0] "" msgstr[1] "" msgid "1 hour" -msgstr "" +msgid_plural "%d hours" +msgstr[0] "" +msgstr[1] "" msgid "1 merged merge request" msgid_plural "%{merge_requests} merged merge requests" @@ -684,6 +686,9 @@ msgstr "" msgid "8 hours" msgstr "" +msgid "< 1 hour" +msgstr "" + msgid "\"johnsmith@example.com\": \"@johnsmith\" will add \"By @johnsmith\" to all issues and comments originally created by johnsmith@example.com, and will set @johnsmith as the assignee on all issues originally assigned to johnsmith@example.com." msgstr "" @@ -1774,6 +1779,9 @@ msgstr "" msgid "An error occurred while loading issues" msgstr "" +msgid "An error occurred while loading merge requests." +msgstr "" + msgid "An error occurred while loading the data. Please try again." msgstr "" @@ -11058,6 +11066,9 @@ msgid_plural "Limited to showing %d events at most" msgstr[0] "" msgstr[1] "" +msgid "Line changes" +msgstr "" + msgid "Link copied" msgstr "" @@ -15835,6 +15846,9 @@ msgstr "" msgid "Review the process for configuring service providers in your identity provider — in this case, GitLab is the \"service provider\" or \"relying party\"." msgstr "" +msgid "Review time" +msgstr "" + msgid "Review time is defined as the time it takes from first comment until merged." msgstr "" @@ -22627,6 +22641,9 @@ msgstr "" msgid "opened %{timeAgoString} by %{user}" msgstr "" +msgid "opened %{timeAgo}" +msgstr "" + msgid "out of %d total test" msgid_plural "out of %d total tests" msgstr[0] "" diff --git a/package.json b/package.json index c2cb19da06c..6bbb284b434 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@babel/plugin-syntax-import-meta": "^7.2.0", "@babel/preset-env": "^7.6.2", "@gitlab/svgs": "^1.89.0", - "@gitlab/ui": "8.15.0", + "@gitlab/ui": "8.17.0", "@gitlab/visual-review-tools": "1.5.1", "@sentry/browser": "^5.10.2", "@sourcegraph/code-host-integration": "^0.0.18", diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index 46aa104fdd7..dd51eac9be1 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -63,7 +63,7 @@ describe 'test coverage badge' do create(:ci_pipeline, opts).tap do |pipeline| yield pipeline - pipeline.update_status + pipeline.update_legacy_status end end diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb index eee3f96ab85..560072a3d83 100644 --- a/spec/lib/gitlab/badge/coverage/report_spec.rb +++ b/spec/lib/gitlab/badge/coverage/report_spec.rb @@ -102,7 +102,7 @@ describe Gitlab::Badge::Coverage::Report do create(:ci_pipeline, opts).tap do |pipeline| yield pipeline - pipeline.update_status + pipeline.update_legacy_status end end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index ce7894ea955..08e57e541a4 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -216,6 +216,7 @@ stages: - project - pipeline - statuses +- processables - builds - bridges statuses: diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index c921e7cadde..01beb5ba33c 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -333,6 +333,7 @@ CommitStatus: - scheduled_at - upstream_pipeline_id - interruptible +- processed Ci::Variable: - id - project_id diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb index f6eee67e539..0987c8e2b65 100644 --- a/spec/models/board_spec.rb +++ b/spec/models/board_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper' describe Board do + let(:project) { create(:project) } + let(:other_project) { create(:project) } + describe 'relationships' do it { is_expected.to belong_to(:project) } it { is_expected.to have_many(:lists).order(list_type: :asc, position: :asc).dependent(:delete_all) } @@ -11,4 +14,28 @@ describe Board do describe 'validations' do it { is_expected.to validate_presence_of(:project) } end + + describe '#order_by_name_asc' do + let!(:second_board) { create(:board, name: 'Secondary board', project: project) } + let!(:first_board) { create(:board, name: 'First board', project: project) } + + it 'returns in alphabetical order' do + expect(project.boards.order_by_name_asc).to eq [first_board, second_board] + end + end + + describe '#first_board' do + let!(:other_board) { create(:board, name: 'Other board', project: other_project) } + let!(:second_board) { create(:board, name: 'Secondary board', project: project) } + let!(:first_board) { create(:board, name: 'First board', project: project) } + + it 'return the first alphabetical board as a relation' do + expect(project.boards.first_board).to eq [first_board] + end + + # BoardsActions#board expects this behavior + it 'raises an error when find is done on a non-existent record' do + expect { project.boards.first_board.find(second_board.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + end end diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index 96d81f4cc49..69fd167e0c8 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -604,7 +604,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do context 'when traces are archived' do let(:subject) do project.builds.each do |build| - build.success! + build.reset.success! end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7c20bb415e1..013581c0d94 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1008,22 +1008,22 @@ describe Ci::Pipeline, :mailer do end end - describe '#duration', :sidekiq_might_not_need_inline do + describe '#duration', :sidekiq_inline do context 'when multiple builds are finished' do before do travel_to(current + 30) do build.run! - build.success! + build.reload.success! build_b.run! build_c.run! end travel_to(current + 40) do - build_b.drop! + build_b.reload.drop! end travel_to(current + 70) do - build_c.success! + build_c.reload.success! end end @@ -1044,7 +1044,7 @@ describe Ci::Pipeline, :mailer do end travel_to(current + 5.minutes) do - build.success! + build.reload.success! end end @@ -1585,6 +1585,30 @@ describe Ci::Pipeline, :mailer do end end + describe '#needs_processing?' do + using RSpec::Parameterized::TableSyntax + + subject { pipeline.needs_processing? } + + where(:processed, :result) do + nil | true + false | true + true | false + end + + with_them do + let(:build) do + create(:ci_build, :success, pipeline: pipeline, name: 'rubocop') + end + + before do + build.update_column(:processed, processed) + end + + it { is_expected.to eq(result) } + end + end + shared_context 'with some outdated pipelines' do before do create_pipeline(:canceled, 'ref', 'A', project) @@ -1785,7 +1809,7 @@ describe Ci::Pipeline, :mailer do it { is_expected.not_to include('created', 'waiting_for_resource', 'preparing', 'pending') } end - describe '#status', :sidekiq_might_not_need_inline do + describe '#status', :sidekiq_inline do let(:build) do create(:ci_build, :created, pipeline: pipeline, name: 'test') end @@ -1826,7 +1850,7 @@ describe Ci::Pipeline, :mailer do context 'on run' do before do build.enqueue - build.run + build.reload.run end it { is_expected.to eq('running') } @@ -1885,7 +1909,7 @@ describe Ci::Pipeline, :mailer do it 'updates does not change pipeline status' do expect(pipeline.statuses.latest.slow_composite_status).to be_nil - expect { pipeline.update_status } + expect { pipeline.update_legacy_status } .to change { pipeline.reload.status } .from('created') .to('skipped') @@ -1898,7 +1922,7 @@ describe Ci::Pipeline, :mailer do end it 'updates pipeline status to running' do - expect { pipeline.update_status } + expect { pipeline.update_legacy_status } .to change { pipeline.reload.status } .from('created') .to('running') @@ -1911,7 +1935,7 @@ describe Ci::Pipeline, :mailer do end it 'updates pipeline status to scheduled' do - expect { pipeline.update_status } + expect { pipeline.update_legacy_status } .to change { pipeline.reload.status } .from('created') .to('scheduled') @@ -1926,7 +1950,7 @@ describe Ci::Pipeline, :mailer do end it 'raises an exception' do - expect { pipeline.update_status } + expect { pipeline.update_legacy_status } .to raise_error(HasStatus::UnknownStatusError) end end @@ -2214,11 +2238,11 @@ describe Ci::Pipeline, :mailer do stub_full_request(hook.url, method: :post) end - context 'with multiple builds', :sidekiq_might_not_need_inline do + context 'with multiple builds', :sidekiq_inline do context 'when build is queued' do before do - build_a.enqueue - build_b.enqueue + build_a.reload.enqueue + build_b.reload.enqueue end it 'receives a pending event once' do @@ -2228,10 +2252,10 @@ describe Ci::Pipeline, :mailer do context 'when build is run' do before do - build_a.enqueue - build_a.run - build_b.enqueue - build_b.run + build_a.reload.enqueue + build_a.reload.run! + build_b.reload.enqueue + build_b.reload.run! end it 'receives a running event once' do @@ -2292,6 +2316,7 @@ describe Ci::Pipeline, :mailer do :created, pipeline: pipeline, name: name, + stage: "stage:#{stage_idx}", stage_idx: stage_idx) end end diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb new file mode 100644 index 00000000000..87dbcbf870e --- /dev/null +++ b/spec/models/ci/processable_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::Processable do + set(:project) { create(:project) } + set(:pipeline) { create(:ci_pipeline, project: project) } + + describe '#aggregated_needs_names' do + let(:with_aggregated_needs) { pipeline.processables.select_with_aggregated_needs(project) } + + context 'with created status' do + let!(:processable) { create(:ci_build, :created, project: project, pipeline: pipeline) } + + context 'with needs' do + before do + create(:ci_build_need, build: processable, name: 'test1') + create(:ci_build_need, build: processable, name: 'test2') + end + + it 'returns all processables' do + expect(with_aggregated_needs).to contain_exactly(processable) + end + + it 'returns all needs' do + expect(with_aggregated_needs.first.aggregated_needs_names).to contain_exactly('test1', 'test2') + end + + context 'with ci_dag_support disabled' do + before do + stub_feature_flags(ci_dag_support: false) + end + + it 'returns all processables' do + expect(with_aggregated_needs).to contain_exactly(processable) + end + + it 'returns empty needs' do + expect(with_aggregated_needs.first.aggregated_needs_names).to be_nil + end + end + end + + context 'without needs' do + it 'returns all processables' do + expect(with_aggregated_needs).to contain_exactly(processable) + end + + it 'returns empty needs' do + expect(with_aggregated_needs.first.aggregated_needs_names).to be_nil + end + end + end + end +end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 7e2751128e2..3aeaa27abce 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -63,7 +63,7 @@ describe Ci::Stage, :models do end it 'updates stage status correctly' do - expect { stage.update_status } + expect { stage.update_legacy_status } .to change { stage.reload.status } .to eq 'running' end @@ -87,7 +87,7 @@ describe Ci::Stage, :models do end it 'updates status to skipped' do - expect { stage.update_status } + expect { stage.update_legacy_status } .to change { stage.reload.status } .to eq 'skipped' end @@ -99,7 +99,7 @@ describe Ci::Stage, :models do end it 'updates status to scheduled' do - expect { stage.update_status } + expect { stage.update_legacy_status } .to change { stage.reload.status } .to 'scheduled' end @@ -111,7 +111,7 @@ describe Ci::Stage, :models do end it 'updates status to waiting for resource' do - expect { stage.update_status } + expect { stage.update_legacy_status } .to change { stage.reload.status } .to 'waiting_for_resource' end @@ -119,7 +119,7 @@ describe Ci::Stage, :models do context 'when stage is skipped because is empty' do it 'updates status to skipped' do - expect { stage.update_status } + expect { stage.update_legacy_status } .to change { stage.reload.status } .to eq('skipped') end @@ -133,7 +133,7 @@ describe Ci::Stage, :models do it 'retries a lock to update a stage status' do stage.lock_version = 100 - stage.update_status + stage.update_legacy_status expect(stage.reload).to be_failed end @@ -147,7 +147,7 @@ describe Ci::Stage, :models do end it 'raises an exception' do - expect { stage.update_status } + expect { stage.update_legacy_status } .to raise_error(HasStatus::UnknownStatusError) end end @@ -179,7 +179,7 @@ describe Ci::Stage, :models do stage_id: stage.id, status: status) - stage.update_status + stage.update_legacy_status end end @@ -196,7 +196,7 @@ describe Ci::Stage, :models do status: :failed, allow_failure: true) - stage.update_status + stage.update_legacy_status end it 'is passed with warnings' do @@ -243,7 +243,7 @@ describe Ci::Stage, :models do it 'recalculates index before updating status' do expect(stage.reload.position).to be_nil - stage.update_status + stage.update_legacy_status expect(stage.reload.position).to eq 10 end @@ -253,7 +253,7 @@ describe Ci::Stage, :models do it 'fallbacks to zero' do expect(stage.reload.position).to be_nil - stage.update_status + stage.update_legacy_status expect(stage.reload.position).to eq 0 end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 98dc6f00412..40652614101 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -63,6 +63,42 @@ describe CommitStatus do end end + describe '#processed' do + subject { commit_status.processed } + + context 'when ci_atomic_processing is disabled' do + before do + stub_feature_flags(ci_atomic_processing: false) + + commit_status.save! + end + + it { is_expected.to be_nil } + end + + context 'when ci_atomic_processing is enabled' do + before do + stub_feature_flags(ci_atomic_processing: true) + end + + context 'status is latest' do + before do + commit_status.update!(retried: false, status: :pending) + end + + it { is_expected.to be_falsey } + end + + context 'status is retried' do + before do + commit_status.update!(retried: true, status: :pending) + end + + it { is_expected.to be_truthy } + end + end + end + describe '#started?' do subject { commit_status.started? } diff --git a/spec/models/project_services/chat_message/base_message_spec.rb b/spec/models/project_services/chat_message/base_message_spec.rb new file mode 100644 index 00000000000..8f80cf0b074 --- /dev/null +++ b/spec/models/project_services/chat_message/base_message_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ChatMessage::BaseMessage do + let(:base_message) { described_class.new(args) } + let(:args) { { project_url: 'https://gitlab-domain.com' } } + + describe '#fallback' do + subject { base_message.fallback } + + before do + allow(base_message).to receive(:message).and_return(message) + end + + context 'without relative links' do + let(:message) { 'Just another *markdown* message' } + + it { is_expected.to eq(message) } + end + + context 'with relative links' do + let(:message) { 'Check this out ![Screenshot1](/uploads/Screenshot1.png)' } + + it { is_expected.to eq('Check this out https://gitlab-domain.com/uploads/Screenshot1.png') } + end + + context 'with multiple relative links' do + let(:message) { 'Check this out ![Screenshot1](/uploads/Screenshot1.png). And this ![Screenshot2](/uploads/Screenshot2.png)' } + + it { is_expected.to eq('Check this out https://gitlab-domain.com/uploads/Screenshot1.png. And this https://gitlab-domain.com/uploads/Screenshot2.png') } + end + end +end diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb index c9d372ea166..4eb023907fa 100644 --- a/spec/services/boards/list_service_spec.rb +++ b/spec/services/boards/list_service_spec.rb @@ -10,6 +10,7 @@ describe Boards::ListService do subject(:service) { described_class.new(parent, double) } it_behaves_like 'boards list service' + it_behaves_like 'multiple boards list service' end context 'when board parent is a group' do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index dc67efe0fbe..d6cc233088d 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -362,11 +362,11 @@ describe Ci::CreatePipelineService do context 'when build that is not marked as interruptible is running' do it 'cancels running outdated pipelines', :sidekiq_might_not_need_inline do - pipeline_on_previous_commit - .builds - .find_by_name('build_2_1') - .tap(&:enqueue!) - .run! + build_2_1 = pipeline_on_previous_commit + .builds.find_by_name('build_2_1') + + build_2_1.enqueue! + build_2_1.reset.run! pipeline @@ -377,12 +377,12 @@ describe Ci::CreatePipelineService do end context 'when an uninterruptible build is running' do - it 'does not cancel running outdated pipelines', :sidekiq_might_not_need_inline do - pipeline_on_previous_commit - .builds - .find_by_name('build_3_1') - .tap(&:enqueue!) - .run! + it 'does not cancel running outdated pipelines', :sidekiq_inline do + build_3_1 = pipeline_on_previous_commit + .builds.find_by_name('build_3_1') + + build_3_1.enqueue! + build_3_1.reset.run! pipeline diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb new file mode 100644 index 00000000000..c29c56c2b04 --- /dev/null +++ b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do + using RSpec::Parameterized::TableSyntax + + set(:pipeline) { create(:ci_pipeline) } + set(:build_a) { create(:ci_build, :success, name: 'build-a', stage: 'build', stage_idx: 0, pipeline: pipeline) } + set(:build_b) { create(:ci_build, :failed, name: 'build-b', stage: 'build', stage_idx: 0, pipeline: pipeline) } + set(:test_a) { create(:ci_build, :running, name: 'test-a', stage: 'test', stage_idx: 1, pipeline: pipeline) } + set(:test_b) { create(:ci_build, :pending, name: 'test-b', stage: 'test', stage_idx: 1, pipeline: pipeline) } + set(:deploy) { create(:ci_build, :created, name: 'deploy', stage: 'deploy', stage_idx: 2, pipeline: pipeline) } + + let(:collection) { described_class.new(pipeline) } + + describe '#set_processable_status' do + it 'does update existing status of processable' do + collection.set_processable_status(test_a.id, 'success', 100) + + expect(collection.status_for_names(['test-a'])).to eq('success') + end + + it 'ignores a missing processable' do + collection.set_processable_status(-1, 'failed', 100) + end + end + + describe '#status_of_all' do + it 'returns composite status of the collection' do + expect(collection.status_of_all).to eq('running') + end + end + + describe '#status_for_names' do + where(:names, :status) do + %w[build-a] | 'success' + %w[build-a build-b] | 'failed' + %w[build-a test-a] | 'running' + end + + with_them do + it 'returns composite status of given names' do + expect(collection.status_for_names(names)).to eq(status) + end + end + end + + describe '#status_for_prior_stage_position' do + where(:stage, :status) do + 0 | 'success' + 1 | 'failed' + 2 | 'running' + end + + with_them do + it 'returns composite status for processables in prior stages' do + expect(collection.status_for_prior_stage_position(stage)).to eq(status) + end + end + end + + describe '#status_for_stage_position' do + where(:stage, :status) do + 0 | 'failed' + 1 | 'running' + 2 | 'created' + end + + with_them do + it 'returns composite status for processables at a given stages' do + expect(collection.status_for_stage_position(stage)).to eq(status) + end + end + end + + describe '#created_processable_ids_for_stage_position' do + it 'returns IDs of processables at a given stage position' do + expect(collection.created_processable_ids_for_stage_position(0)).to be_empty + expect(collection.created_processable_ids_for_stage_position(1)).to be_empty + expect(collection.created_processable_ids_for_stage_position(2)).to contain_exactly(deploy.id) + end + end + + describe '#processing_processables' do + it 'returns processables marked as processing' do + expect(collection.processing_processables.map { |processable| processable[:id]} ) + .to contain_exactly(build_a.id, build_b.id, test_a.id, test_b.id, deploy.id) + end + end +end diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb new file mode 100644 index 00000000000..38686b41a22 --- /dev/null +++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative 'shared_processing_service.rb' + +describe Ci::PipelineProcessing::AtomicProcessingService do + before do + stub_feature_flags(ci_atomic_processing: true) + end + + it_behaves_like 'Pipeline Processing Service' +end diff --git a/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb b/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb index 6e92771b034..2da1eb19818 100644 --- a/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb +++ b/spec/services/ci/pipeline_processing/legacy_processing_service_spec.rb @@ -4,5 +4,9 @@ require 'spec_helper' require_relative 'shared_processing_service.rb' describe Ci::PipelineProcessing::LegacyProcessingService do + before do + stub_feature_flags(ci_atomic_processing: false) + end + it_behaves_like 'Pipeline Processing Service' end diff --git a/spec/services/ci/pipeline_processing/shared_processing_service.rb b/spec/services/ci/pipeline_processing/shared_processing_service.rb index 45baae4118c..cae5ae3f09d 100644 --- a/spec/services/ci/pipeline_processing/shared_processing_service.rb +++ b/spec/services/ci/pipeline_processing/shared_processing_service.rb @@ -879,19 +879,27 @@ shared_examples 'Pipeline Processing Service' do end def succeed_pending - builds.pending.map(&:success) + builds.pending.each do |build| + build.reset.success + end end def succeed_running_or_pending - pipeline.builds.running_or_pending.each(&:success) + pipeline.builds.running_or_pending.each do |build| + build.reset.success + end end def fail_running_or_pending - pipeline.builds.running_or_pending.each(&:drop) + pipeline.builds.running_or_pending.each do |build| + build.reset.drop + end end def cancel_running_or_pending - pipeline.builds.running_or_pending.each(&:cancel) + pipeline.builds.running_or_pending.each do |build| + build.reset.cancel + end end def play_manual_action(name) @@ -911,11 +919,15 @@ shared_examples 'Pipeline Processing Service' do end def create_build(name, **opts) - create(:ci_build, :created, pipeline: pipeline, name: name, **opts) + create(:ci_build, :created, pipeline: pipeline, name: name, **with_stage_opts(opts)) end def successful_build(name, **opts) - create(:ci_build, :success, pipeline: pipeline, name: name, **opts) + create(:ci_build, :success, pipeline: pipeline, name: name, **with_stage_opts(opts)) + end + + def with_stage_opts(opts) + { stage: "stage-#{opts[:stage_idx].to_i}" }.merge(opts) end def delayed_options diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 1a39b37e925..b3189974440 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -45,7 +45,8 @@ describe Ci::RetryBuildService do user_id auto_canceled_by_id retried failure_reason sourced_pipelines artifacts_file_store artifacts_metadata_store metadata runner_session trace_chunks upstream_pipeline_id - artifacts_file artifacts_metadata artifacts_size commands resource resource_group_id].freeze + artifacts_file artifacts_metadata artifacts_size commands + resource resource_group_id processed].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } @@ -202,12 +203,13 @@ describe Ci::RetryBuildService do it 'does not enqueue the new build' do expect(new_build).to be_created + expect(new_build).not_to be_processed end - it 'does mark old build as retried in the database and on the instance' do + it 'does mark old build as retried' do expect(new_build).to be_latest expect(build).to be_retried - expect(build.reload).to be_retried + expect(build).to be_processed end context 'when build with deployment is retried' do diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 4b949761b8f..e7a241ed335 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -330,7 +330,7 @@ describe Ci::RetryPipelineService, '#execute' do stage: "stage_#{stage_num}", stage_idx: stage_num, pipeline: pipeline, **opts) do |build| - pipeline.update_status + pipeline.update_legacy_status end end end diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb index 25dc2e04942..18d45ee324a 100644 --- a/spec/support/shared_examples/services/boards/boards_list_service.rb +++ b/spec/support/shared_examples/services/boards/boards_list_service.rb @@ -29,3 +29,20 @@ shared_examples 'boards list service' do expect(service.execute).to eq [board] end end + +shared_examples 'multiple boards list service' do + let(:service) { described_class.new(parent, double) } + let!(:board_B) { create(:board, resource_parent: parent, name: 'B-board') } + let!(:board_c) { create(:board, resource_parent: parent, name: 'c-board') } + let!(:board_a) { create(:board, resource_parent: parent, name: 'a-board') } + + describe '#execute' do + it 'returns all issue boards' do + expect(service.execute.size).to eq(3) + end + + it 'returns boards ordered by name' do + expect(service.execute).to eq [board_a, board_B, board_c] + end + end +end diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb index 0225e4a9601..187298034cc 100644 --- a/spec/workers/pipeline_update_worker_spec.rb +++ b/spec/workers/pipeline_update_worker_spec.rb @@ -8,7 +8,7 @@ describe PipelineUpdateWorker do let(:pipeline) { create(:ci_pipeline) } it 'updates pipeline status' do - expect_any_instance_of(Ci::Pipeline).to receive(:update_status) + expect_any_instance_of(Ci::Pipeline).to receive(:set_status).with('skipped') described_class.new.perform(pipeline.id) end diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb index 429d42bac29..8a57cc6bbff 100644 --- a/spec/workers/stage_update_worker_spec.rb +++ b/spec/workers/stage_update_worker_spec.rb @@ -8,7 +8,7 @@ describe StageUpdateWorker do let(:stage) { create(:ci_stage_entity) } it 'updates stage status' do - expect_any_instance_of(Ci::Stage).to receive(:update_status) + expect_any_instance_of(Ci::Stage).to receive(:set_status).with('skipped') described_class.new.perform(stage.id) end diff --git a/yarn.lock b/yarn.lock index 43571c8d09a..559c9659f42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -737,10 +737,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.89.0.tgz#5bdaff1b0af1cc07ed34e89c21c34c7c6a3e1caa" integrity sha512-vI6VobZs6mq2Bbiej5bYMHyvtn8kD1O/uHSlyY9jgJoa2TXU+jFI9DqUpJmx8EIHt+o0qm/8G3XsFGEr5gLb7Q== -"@gitlab/ui@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-8.15.0.tgz#51fa3f2b4ccb8454bcb9680acb334bc88fe15f3d" - integrity sha512-M9hnLVRMUF5DDfwPtR5CLsCyiWgjslqg2p37a6qwjdjZ+ST5t0Vr/44Mg4Lz4y2zxqjDaSmR4KtmipvykeQx1A== +"@gitlab/ui@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-8.17.0.tgz#674baa9b5c05fa6ecb23b233c5b308ff82ba5660" + integrity sha512-klWzMFU3IdoLUgRP6OTYUyO+EDfckG9/cphPKVBaf0MLx4HpjiW5LwGW3stL3A9SlyauCwAZOLkqbJKbN5pxCQ== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0"