summaryrefslogtreecommitdiff
path: root/app/models/ci/pipeline.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/ci/pipeline.rb')
-rw-r--r--app/models/ci/pipeline.rb97
1 files changed, 77 insertions, 20 deletions
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3fee6c18770..fab8497ec7d 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -21,8 +21,6 @@ module Ci
after_create :keep_around_commits, unless: :importing?
- delegate :stages, to: :statuses
-
state_machine :status, initial: :created do
event :enqueue do
transition created: :pending
@@ -90,25 +88,66 @@ module Ci
end
# ref can't be HEAD or SHA, can only be branch/tag name
+ scope :latest, ->(ref = nil) do
+ max_id = unscope(:select)
+ .select("max(#{quoted_table_name}.id)")
+ .group(:ref, :sha)
+
+ relation = ref ? where(ref: ref) : self
+ relation.where(id: max_id)
+ end
+
+ def self.latest_status(ref = nil)
+ latest(ref).status
+ end
+
def self.latest_successful_for(ref)
- where(ref: ref).order(id: :desc).success.first
+ success.latest(ref).order(id: :desc).first
end
def self.truncate_sha(sha)
sha[0...8]
end
- def self.stages
- # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
- CommitStatus.where(pipeline: pluck(:id)).stages
- end
-
def self.total_duration
where.not(duration: nil).sum(:duration)
end
- def stages_with_latest_statuses
- statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage)
+ def stage(name)
+ stage = Ci::Stage.new(self, name: name)
+ stage unless stage.statuses_count.zero?
+ end
+
+ def stages_count
+ statuses.select(:stage).distinct.count
+ end
+
+ def stages_name
+ statuses.order(:stage_idx).distinct.
+ pluck(:stage, :stage_idx).map(&:first)
+ end
+
+ def stages
+ # TODO, this needs refactoring, see gitlab-ce#26481.
+
+ stages_query = statuses
+ .group('stage').select(:stage).order('max(stage_idx)')
+
+ status_sql = statuses.latest.where('stage=sg.stage').status_sql
+
+ warnings_sql = statuses.latest.select('COUNT(*) > 0')
+ .where('stage=sg.stage').failed_but_allowed.to_sql
+
+ stages_with_statuses = CommitStatus.from(stages_query, :sg)
+ .pluck('sg.stage', status_sql, "(#{warnings_sql})")
+
+ stages_with_statuses.map do |stage|
+ Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)])
+ end
+ end
+
+ def artifacts
+ builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
def project_id
@@ -157,27 +196,35 @@ module Ci
end
def manual_actions
- builds.latest.manual_actions
+ builds.latest.manual_actions.includes(project: [:namespace])
+ end
+
+ def stuck?
+ builds.pending.any?(&:stuck?)
end
def retryable?
- builds.latest.any? do |build|
- (build.failed? || build.canceled?) && build.retryable?
- end
+ builds.latest.failed_or_canceled.any?(&:retryable?)
end
def cancelable?
- builds.running_or_pending.any?
+ statuses.cancelable.any?
end
def cancel_running
- builds.running_or_pending.each(&:cancel)
+ Gitlab::OptimisticLocking.retry_lock(
+ statuses.cancelable) do |cancelable|
+ cancelable.each(&:cancel)
+ end
end
def retry_failed(user)
- builds.latest.failed.select(&:retryable?).each do |build|
- Ci::Build.retry(build, user)
- end
+ Gitlab::OptimisticLocking.retry_lock(
+ builds.latest.failed_or_canceled) do |failed_or_canceled|
+ failed_or_canceled.select(&:retryable?).each do |build|
+ Ci::Build.retry(build, user)
+ end
+ end
end
def mark_as_processable_after_stage(stage_idx)
@@ -245,6 +292,10 @@ module Ci
end
end
+ def has_yaml_errors?
+ yaml_errors.present?
+ end
+
def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq
end
@@ -313,7 +364,13 @@ module Ci
def merge_requests
@merge_requests ||= project.merge_requests
.where(source_branch: self.ref)
- .select { |merge_request| merge_request.pipeline.try(:id) == self.id }
+ .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
+ end
+
+ def detailed_status(current_user)
+ Gitlab::Ci::Status::Pipeline::Factory
+ .new(self, current_user)
+ .fabricate!
end
private