1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
# frozen_string_literal: true
module Ci
class Stage < Ci::ApplicationRecord
include Ci::Partitionable
include Ci::HasStatus
include Importable
include Gitlab::OptimisticLocking
include Presentable
self.table_name = :p_ci_stages
self.primary_key = :id
self.sequence_name = :ci_job_stages_id_seq
query_constraints :id, :partition_id
partitionable scope: :pipeline, partitioned: true
enum status: Ci::HasStatus::STATUSES_ENUM
belongs_to :project
belongs_to :pipeline, ->(stage) { in_partition(stage) },
foreign_key: :pipeline_id, partition_foreign_key: :partition_id, inverse_of: :stages
has_many :statuses,
->(stage) { in_partition(stage) },
class_name: 'CommitStatus',
foreign_key: :stage_id,
partition_foreign_key: :partition_id,
inverse_of: :ci_stage
has_many :latest_statuses,
->(stage) { in_partition(stage).ordered.latest },
class_name: 'CommitStatus',
foreign_key: :stage_id,
partition_foreign_key: :partition_id,
inverse_of: :ci_stage
has_many :retried_statuses,
->(stage) { in_partition(stage).ordered.retried },
class_name: 'CommitStatus',
foreign_key: :stage_id,
partition_foreign_key: :partition_id,
inverse_of: :ci_stage
has_many :processables,
->(stage) { in_partition(stage) },
class_name: 'Ci::Processable',
foreign_key: :stage_id,
partition_foreign_key: :partition_id,
inverse_of: :ci_stage
has_many :builds,
->(stage) { in_partition(stage) },
foreign_key: :stage_id,
partition_foreign_key: :partition_id,
inverse_of: :ci_stage
has_many :bridges,
->(stage) { in_partition(stage) },
foreign_key: :stage_id,
partition_foreign_key: :partition_id,
inverse_of: :ci_stage
has_many :generic_commit_statuses,
->(stage) { in_partition(stage) },
foreign_key: :stage_id,
partition_foreign_key: :partition_id,
inverse_of: :ci_stage
scope :ordered, -> { order(position: :asc) }
scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) }
scope :by_name, ->(names) { where(name: names) }
scope :by_position, ->(positions) { where(position: positions) }
with_options unless: :importing? do
validates :project, presence: true
validates :pipeline, presence: true
validates :name, presence: true
validates :position, presence: true
end
after_initialize do
self.status = DEFAULT_STATUS if self.status.nil?
end
before_validation unless: :importing? do
next if position.present?
self.position = statuses.select(:stage_idx)
.where.not(stage_idx: nil)
.group(:stage_idx)
.order('COUNT(id) DESC')
.first&.stage_idx.to_i
end
state_machine :status, initial: :created do
event :enqueue do
transition any - [:pending] => :pending
end
event :request_resource do
transition any - [:waiting_for_resource] => :waiting_for_resource
end
event :prepare do
transition any - [:preparing] => :preparing
end
event :run do
transition any - [:running] => :running
end
event :wait_for_callback do
transition any - [:waiting_for_callback] => :waiting_for_callback
end
event :skip do
transition any - [:skipped] => :skipped
end
event :drop do
transition any - [:failed] => :failed
end
event :succeed do
transition any - [:success] => :success
end
event :start_cancel do
transition any - [:canceling, :canceled] => :canceling
end
event :cancel do
transition any - [:canceled] => :canceled
end
event :block do
transition any - [:manual] => :manual
end
event :delay do
transition any - [:scheduled] => :scheduled
end
end
# rubocop: disable Metrics/CyclomaticComplexity -- breaking apart hurts readability, consider refactoring issue #439268
def set_status(new_status)
retry_optimistic_lock(self, name: 'ci_stage_set_status') do
case new_status
when 'created' then nil
when 'waiting_for_resource' then request_resource
when 'preparing' then prepare
when 'waiting_for_callback' then wait_for_callback
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
when 'failed' then drop
when 'canceling' then start_cancel
when 'canceled' then cancel
when 'manual' then block
when 'scheduled' then delay
when 'skipped', nil then skip
else
raise Ci::HasStatus::UnknownStatusError, "Unknown status `#{new_status}`"
end
end
end
# rubocop: enable Metrics/CyclomaticComplexity
# This will be removed with ci_remove_ensure_stage_service
def update_legacy_status
set_status(latest_stage_status.to_s)
end
def groups
@groups ||= Ci::Group.fabricate(project, self)
end
def has_warnings?
number_of_warnings > 0
end
def number_of_warnings
BatchLoader.for(id).batch(default_value: 0) do |stage_ids, loader|
::CommitStatus.where(stage_id: stage_ids)
.latest
.failed_but_allowed
.group(:stage_id)
.count
.each { |id, amount| loader.call(id, amount) }
end
end
def detailed_status(current_user)
Gitlab::Ci::Status::Stage::Factory
.new(self, current_user)
.fabricate!
end
def manual_playable?
blocked? || skipped?
end
# We only check jobs that are played by `Ci::PlayManualStageService`.
def confirm_manual_job?
processables.manual.any? do |job|
job.playable? && job.manual_confirmation_message
end
end
# This will be removed with ci_remove_ensure_stage_service
def latest_stage_status
statuses.latest.composite_status || 'skipped'
end
def ordered_latest_statuses
preload_metadata(statuses.in_order_of(:status, Ci::HasStatus::ORDERED_STATUSES).latest_ordered)
end
def ordered_retried_statuses
preload_metadata(statuses.in_order_of(:status, Ci::HasStatus::ORDERED_STATUSES).retried_ordered)
end
private
def preload_metadata(statuses)
relations = [:metadata, :pipeline, { downstream_pipeline: [:user, { project: [:route, { namespace: :route }] }] }]
Preloaders::CommitStatusPreloader.new(statuses).execute(relations)
statuses
end
end
end
|