commit_status.rb 5.23 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
8
  include EnumWithNil
9

10 11
  self.table_name = 'ci_builds'

12
  belongs_to :user
13
  belongs_to :project
14
  belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
15
  belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
16

17
  delegate :commit, to: :pipeline
Douwe Maan's avatar
Douwe Maan committed
18
  delegate :sha, :short_sha, to: :pipeline
19

20
  validates :pipeline, presence: true, unless: :importing?
21
  validates :name, presence: true, unless: :importing?
22

23
  alias_attribute :author, :user
24
  alias_attribute :pipeline_id, :commit_id
25

26
  scope :failed_but_allowed, -> do
27
    where(allow_failure: true, status: [:failed, :canceled])
28
  end
Shinya Maeda's avatar
Shinya Maeda committed
29

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

38
  scope :latest, -> { where(retried: [false, nil]) }
39
  scope :retried, -> { where(retried: true) }
40
  scope :ordered, -> { order(:name) }
41 42
  scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
  scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
43
  scope :after_stage, -> (index) { where('stage_idx > ?', index) }
44
  scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
45

46 47 48
  # 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
49

50 51 52 53 54
  ##
  # We still create some CommitStatuses outside of CreatePipelineService.
  #
  # These are pages deployments and external statuses.
  #
55
  before_create unless: :importing? do
56
    # rubocop: disable CodeReuse/ServiceClass
57 58
    Ci::EnsureStageService.new(project, user).execute(self) do |stage|
      self.run_after_commit { StageUpdateWorker.perform_async(stage.id) }
59
    end
60
    # rubocop: enable CodeReuse/ServiceClass
61 62
  end

63
  state_machine :status do
64
    event :process do
65
      transition [:skipped, :manual] => :created
66 67
    end

68
    event :enqueue do
69 70 71 72
      # 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?
73 74
    end

75 76 77 78
    event :run do
      transition pending: :running
    end

79
    event :skip do
80
      transition [:created, :preparing, :pending] => :skipped
81 82
    end

83
    event :drop do
84
      transition [:created, :preparing, :pending, :running, :scheduled] => :failed
85 86 87
    end

    event :success do
88
      transition [:created, :preparing, :pending, :running] => :success
89 90 91
    end

    event :cancel do
92
      transition [:created, :preparing, :pending, :running, :manual, :scheduled] => :canceled
93 94
    end

95
    before_transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
96
      commit_status.queued_at = Time.now
97 98
    end

99
    before_transition [:created, :preparing, :pending] => :running do |commit_status|
100
      commit_status.started_at = Time.now
101 102
    end

103 104
    before_transition any => [:success, :failed, :canceled] do |commit_status|
      commit_status.finished_at = Time.now
105 106
    end

107 108
    before_transition any => :failed do |commit_status, transition|
      failure_reason = transition.args.first
109
      commit_status.failure_reason = CommitStatus.failure_reasons[failure_reason]
110 111
    end

112
    after_transition do |commit_status, transition|
113
      next unless commit_status.project
114
      next if transition.loopback?
115

116
      commit_status.run_after_commit do
117
        if pipeline_id
118
          if complete? || manual?
119
            PipelineProcessWorker.perform_async(pipeline_id)
120
          else
121
            PipelineUpdateWorker.perform_async(pipeline_id)
122
          end
123
        end
124

125 126
        StageUpdateWorker.perform_async(stage_id)
        ExpireJobCacheWorker.perform_async(id)
127
      end
128 129
    end

130
    after_transition any => :failed do |commit_status|
131 132
      next unless commit_status.project

133
      # rubocop: disable CodeReuse/ServiceClass
134
      commit_status.run_after_commit do
135
        MergeRequests::AddTodoWhenBuildFailsService
136
          .new(project, nil).execute(self)
137
      end
138
      # rubocop: enable CodeReuse/ServiceClass
139
    end
140 141
  end

142 143 144 145
  def locking_enabled?
    status_changed?
  end

146
  def before_sha
147
    pipeline.before_sha || Gitlab::Git::BLANK_SHA
148
  end
149

Kamil Trzciński's avatar
Kamil Trzciński committed
150
  def group_name
151
    name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
Kamil Trzciński's avatar
Kamil Trzciński committed
152 153
  end

154
  def failed_but_allowed?
155
    allow_failure? && (failed? || canceled?)
156 157
  end

158 159 160 161
  def duration
    calculate_duration
  end

Kamil Trzciński's avatar
Kamil Trzciński committed
162 163 164 165
  def playable?
    false
  end

166 167 168 169
  def retryable?
    false
  end

170 171 172 173
  def cancelable?
    false
  end

174 175 176 177
  def archived?
    false
  end

178 179
  def stuck?
    false
180
  end
Kamil Trzciński's avatar
Kamil Trzciński committed
181

182
  def has_trace?
183 184
    false
  end
185

186 187 188 189
  def any_unmet_prerequisites?
    false
  end

190 191 192 193
  def auto_canceled?
    canceled? && auto_canceled_by_id?
  end

194
  def detailed_status(current_user)
195 196 197
    Gitlab::Ci::Status::Factory
      .new(self, current_user)
      .fabricate!
198
  end
199

Mike Greiling's avatar
Mike Greiling committed
200
  def sortable_name
201
    name.to_s.split(/(\d+)/).map do |v|
202 203 204
      v =~ /\d+/ ? v.to_i : v
    end
  end
205
end