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
|
# frozen_string_literal: true
module Ci
class Stage < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
include Ci::HasStatus
include Gitlab::OptimisticLocking
enum status: Ci::HasStatus::STATUSES_ENUM
belongs_to :project
belongs_to :pipeline
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :latest_statuses, -> { ordered.latest }, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :retried_statuses, -> { ordered.retried }, 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) }
scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) }
scope :by_name, ->(names) { where(name: names) }
scope :with_latest_and_retried_statuses, -> do
includes(
latest_statuses: [:pipeline, project: :namespace],
retried_statuses: [:pipeline, project: :namespace]
)
end
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(*) 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(project: project) || 'skipped'
end
end
end
|