gitlab-org--gitlab-foss/app/models/ci/stage.rb

158 lines
4.1 KiB
Ruby

# frozen_string_literal: true
module Ci
class Stage < Ci::ApplicationRecord
include Ci::Partitionable
include Importable
include Ci::HasStatus
include Gitlab::OptimisticLocking
include Presentable
partitionable scope: :pipeline
enum status: Ci::HasStatus::STATUSES_ENUM
belongs_to :project
belongs_to :pipeline
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id, inverse_of: :ci_stage
has_many :latest_statuses, -> { ordered.latest },
class_name: 'CommitStatus',
foreign_key: :stage_id,
inverse_of: :ci_stage
has_many :retried_statuses, -> { ordered.retried },
class_name: 'CommitStatus',
foreign_key: :stage_id,
inverse_of: :ci_stage
has_many :processables, class_name: 'Ci::Processable', foreign_key: :stage_id, inverse_of: :ci_stage
has_many :builds, foreign_key: :stage_id, inverse_of: :ci_stage
has_many :bridges, foreign_key: :stage_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 :skip do
transition any - [:skipped] => :skipped
end
event :drop do
transition any - [:failed] => :failed
end
event :succeed do
transition any - [:success] => :success
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
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 'pending' then enqueue
when 'running' then run
when 'success' then succeed
when 'failed' then drop
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
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
def latest_stage_status
statuses.latest.composite_status || 'skipped'
end
end
end