commit_status.rb 6.5 KB
Newer Older
1 2
# frozen_string_literal: true

3
class CommitStatus < ApplicationRecord
4
  include HasStatus
5
  include Importable
6
  include AfterCommitQueue
7
  include Presentable
J
Jan Provaznik 已提交
8
  include EnumWithNil
K
Kamil Trzcinski 已提交
9

10 11
  prepend_if_ee('::EE::CommitStatus') # rubocop: disable Cop/InjectEnterpriseEditionModule

K
Kamil Trzcinski 已提交
12 13
  self.table_name = 'ci_builds'

14
  belongs_to :user
K
Kamil Trzciński 已提交
15
  belongs_to :project
16
  belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
17
  belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
K
Kamil Trzcinski 已提交
18

19
  delegate :commit, to: :pipeline
20
  delegate :sha, :short_sha, :before_sha, to: :pipeline
21

22
  validates :pipeline, presence: true, unless: :importing?
23
  validates :name, presence: true, unless: :importing?
K
Kamil Trzcinski 已提交
24

K
Kamil Trzcinski 已提交
25
  alias_attribute :author, :user
26
  alias_attribute :pipeline_id, :commit_id
27

28
  scope :failed_but_allowed, -> do
29
    where(allow_failure: true, status: [:failed, :canceled])
30
  end
S
Shinya Maeda 已提交
31

32
  scope :exclude_ignored, -> do
33 34 35
    # We want to ignore failed but allowed to fail jobs.
    #
    # TODO, we also skip ignored optional manual actions.
36
    where("allow_failure = ? OR status IN (?)",
37
      false, all_state_names - [:failed, :canceled, :manual])
38
  end
L
Lin Jen-Shin 已提交
39

K
Kamil Trzcinski 已提交
40
  scope :latest, -> { where(retried: [false, nil]) }
41
  scope :retried, -> { where(retried: true) }
42
  scope :ordered, -> { order(:name) }
43 44
  scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
  scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
K
Kamil Trzciński 已提交
45 46
  scope :before_stage, -> (index) { where('stage_idx < ?', index) }
  scope :for_stage, -> (index) { where(stage_idx: index) }
47
  scope :after_stage, -> (index) { where('stage_idx > ?', index) }
K
Kamil Trzciński 已提交
48
  scope :for_ids, -> (ids) { where(id: ids) }
49 50 51 52 53 54
  scope :for_ref, -> (ref) { where(ref: ref) }
  scope :by_name, -> (name) { where(name: name) }

  scope :for_project_paths, -> (paths) do
    where(project: Project.where_full_path_in(Array(paths)))
  end
K
Kamil Trzcinski 已提交
55

56 57 58 59
  scope :with_preloads, -> do
    preload(:project, :user)
  end

K
Kamil Trzciński 已提交
60 61 62 63 64 65
  scope :with_needs, -> (names = nil) do
    needs = Ci::BuildNeed.scoped_build.select(1)
    needs = needs.where(name: names) if names
    where('EXISTS (?)', needs).preload(:needs)
  end

K
Kamil Trzciński 已提交
66 67 68 69
  scope :without_needs, -> (names = nil) do
    needs = Ci::BuildNeed.scoped_build.select(1)
    needs = needs.where(name: names) if names
    where('NOT EXISTS (?)', needs)
70 71
  end

72 73 74
  # 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
75

76 77 78 79 80
  ##
  # We still create some CommitStatuses outside of CreatePipelineService.
  #
  # These are pages deployments and external statuses.
  #
81
  before_create unless: :importing? do
82
    # rubocop: disable CodeReuse/ServiceClass
83 84
    Ci::EnsureStageService.new(project, user).execute(self) do |stage|
      self.run_after_commit { StageUpdateWorker.perform_async(stage.id) }
85
    end
86
    # rubocop: enable CodeReuse/ServiceClass
87 88
  end

89
  state_machine :status do
90
    event :process do
91
      transition [:skipped, :manual] => :created
92 93
    end

94
    event :enqueue do
T
Tiger 已提交
95 96 97
      # A CommitStatus will never have prerequisites, but this event
      # is shared by Ci::Build, which cannot progress unless prerequisites
      # are satisfied.
98
      transition [:created, :skipped, :manual, :scheduled] => :pending, if: :all_met_to_become_pending?
99 100
    end

K
Kamil Trzcinski 已提交
101 102 103 104
    event :run do
      transition pending: :running
    end

105
    event :skip do
106
      transition [:created, :waiting_for_resource, :preparing, :pending] => :skipped
107 108
    end

K
Kamil Trzcinski 已提交
109
    event :drop do
110
      transition [:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled] => :failed
K
Kamil Trzcinski 已提交
111 112 113
    end

    event :success do
114
      transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success
K
Kamil Trzcinski 已提交
115 116 117
    end

    event :cancel do
118
      transition [:created, :waiting_for_resource, :preparing, :pending, :running, :manual, :scheduled] => :canceled
K
Kamil Trzcinski 已提交
119 120
    end

121
    before_transition [:created, :waiting_for_resource, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
K
Kamil Trzcinski 已提交
122
      commit_status.queued_at = Time.now
K
Kamil Trzcinski 已提交
123 124
    end

T
Tiger 已提交
125
    before_transition [:created, :preparing, :pending] => :running do |commit_status|
K
Kamil Trzcinski 已提交
126
      commit_status.started_at = Time.now
K
Kamil Trzcinski 已提交
127 128
    end

K
Kamil Trzcinski 已提交
129 130
    before_transition any => [:success, :failed, :canceled] do |commit_status|
      commit_status.finished_at = Time.now
K
Kamil Trzcinski 已提交
131 132
    end

133 134
    before_transition any => :failed do |commit_status, transition|
      failure_reason = transition.args.first
135
      commit_status.failure_reason = CommitStatus.failure_reasons[failure_reason]
136 137
    end

138
    after_transition do |commit_status, transition|
139
      next unless commit_status.project
140
      next if transition.loopback?
141

142
      commit_status.run_after_commit do
143
        if pipeline_id
144
          if complete? || manual?
145
            PipelineProcessWorker.perform_async(pipeline_id, [id])
146
          else
147
            PipelineUpdateWorker.perform_async(pipeline_id)
148
          end
149
        end
150

151 152
        StageUpdateWorker.perform_async(stage_id)
        ExpireJobCacheWorker.perform_async(id)
153
      end
154 155
    end

156
    after_transition any => :failed do |commit_status|
157 158
      next unless commit_status.project

159
      # rubocop: disable CodeReuse/ServiceClass
160
      commit_status.run_after_commit do
D
Douwe Maan 已提交
161
        MergeRequests::AddTodoWhenBuildFailsService
162
          .new(project, nil).execute(self)
163
      end
164
      # rubocop: enable CodeReuse/ServiceClass
165
    end
K
Kamil Trzcinski 已提交
166 167
  end

K
Kamil Trzciński 已提交
168 169 170 171 172
  def self.names
    select(:name)
  end

  def self.status_for_prior_stages(index)
173
    before_stage(index).latest.slow_composite_status || 'success'
K
Kamil Trzciński 已提交
174 175 176
  end

  def self.status_for_names(names)
177
    where(name: names).latest.slow_composite_status || 'success'
K
Kamil Trzciński 已提交
178 179
  end

180
  def locking_enabled?
181
    will_save_change_to_status?
182 183
  end

K
Kamil Trzcinski 已提交
184
  def group_name
185
    name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
K
Kamil Trzcinski 已提交
186 187
  end

188
  def failed_but_allowed?
189
    allow_failure? && (failed? || canceled?)
190 191
  end

192 193 194 195
  def duration
    calculate_duration
  end

K
Kamil Trzcinski 已提交
196 197 198 199
  def playable?
    false
  end

200 201 202 203
  def retryable?
    false
  end

204 205 206 207
  def cancelable?
    false
  end

208 209 210 211
  def archived?
    false
  end

212 213
  def stuck?
    false
K
Kamil Trzcinski 已提交
214
  end
K
Kamil Trzcinski 已提交
215

216
  def has_trace?
217 218
    false
  end
K
Kamil Trzcinski 已提交
219

220 221 222 223
  def all_met_to_become_pending?
    !any_unmet_prerequisites? && !requires_resource?
  end

T
Tiger 已提交
224 225 226 227
  def any_unmet_prerequisites?
    false
  end

228 229 230 231
  def requires_resource?
    false
  end

232 233 234 235
  def auto_canceled?
    canceled? && auto_canceled_by_id?
  end

236
  def detailed_status(current_user)
D
Douwe Maan 已提交
237 238 239
    Gitlab::Ci::Status::Factory
      .new(self, current_user)
      .fabricate!
K
Kamil Trzcinski 已提交
240
  end
241

M
Mike Greiling 已提交
242
  def sortable_name
243
    name.to_s.split(/(\d+)/).map do |v|
244 245 246
      v =~ /\d+/ ? v.to_i : v
    end
  end
K
Kamil Trzcinski 已提交
247
end