commit_status.rb 6.3 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) }
48
  scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
K
Kamil Trzciński 已提交
49
  scope :for_ids, -> (ids) { where(id: ids) }
50 51 52 53 54 55
  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 已提交
56

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

K
Kamil Trzciński 已提交
61 62 63 64 65 66
  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 已提交
67 68 69 70
  scope :without_needs, -> (names = nil) do
    needs = Ci::BuildNeed.scoped_build.select(1)
    needs = needs.where(name: names) if names
    where('NOT EXISTS (?)', needs)
71 72
  end

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

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

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

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

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

106
    event :skip do
T
Tiger 已提交
107
      transition [:created, :preparing, :pending] => :skipped
108 109
    end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

181
  def locking_enabled?
182
    will_save_change_to_status?
183 184
  end

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

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

193 194 195 196
  def duration
    calculate_duration
  end

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

201 202 203 204
  def retryable?
    false
  end

205 206 207 208
  def cancelable?
    false
  end

209 210 211 212
  def archived?
    false
  end

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

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

T
Tiger 已提交
221 222 223 224
  def any_unmet_prerequisites?
    false
  end

225 226 227 228
  def auto_canceled?
    canceled? && auto_canceled_by_id?
  end

229
  def detailed_status(current_user)
D
Douwe Maan 已提交
230 231 232
    Gitlab::Ci::Status::Factory
      .new(self, current_user)
      .fabricate!
K
Kamil Trzcinski 已提交
233
  end
234

M
Mike Greiling 已提交
235
  def sortable_name
236
    name.to_s.split(/(\d+)/).map do |v|
237 238 239
      v =~ /\d+/ ? v.to_i : v
    end
  end
K
Kamil Trzcinski 已提交
240
end