summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2016-12-08 12:41:23 +0100
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2016-12-08 12:41:23 +0100
commitfc57b3144ff99be7e87a2d6f5172fab0dbfd8841 (patch)
tree8c3f9ce23965ff63483fbb1a8e41b2168afe3478
parentf7335aa5cb1ed368f0583d7dca8fcfa7574d6ad9 (diff)
parent65f3206024778b934171c9d9ece8ab627ba6c4c5 (diff)
downloadgitlab-ce-fc57b3144ff99be7e87a2d6f5172fab0dbfd8841.tar.gz
Merge branch 'build-statuses' into 22604-manual-actions
* build-statuses: (21 commits) Remove ci_status_with_icon helper and replace it with partial Check permission of details Introduce `cancelable` and `returnable` Improve actions Added Ci::Status::Build Fix specs Move .pipeline-graph to pipelines/graph Code review Add Ci::Status::Factory Fix success status Added Ci::Stage specs Update stage rendering views Fix handling of allowed to failure jobs Fix handling of skipped vs success status Fix test failures Added Stage tests Add Ci::Status::Stage Preserve stage values and use StaticModel Introduce `Ci::Stage`, right now this is artificial object that is build dynamically. Fix broken pipeline rendering ...
-rw-r--r--app/helpers/ci_status_helper.rb20
-rw-r--r--app/models/ci/build.rb8
-rw-r--r--app/models/ci/pipeline.rb34
-rw-r--r--app/models/ci/stage.rb37
-rw-r--r--app/models/commit_status.rb29
-rw-r--r--app/models/concerns/has_status.rb5
-rw-r--r--app/services/ci/process_pipeline_service.rb4
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/ci/status/_icon_with_label.html.haml10
-rw-r--r--app/views/notify/pipeline_success_email.html.haml2
-rw-r--r--app/views/notify/pipeline_success_email.text.erb2
-rw-r--r--app/views/projects/builds/_header.html.haml2
-rw-r--r--app/views/projects/builds/_sidebar.html.haml4
-rw-r--r--app/views/projects/ci/builds/_build.html.haml13
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml17
-rw-r--r--app/views/projects/commit/_ci_stage.html.haml15
-rw-r--r--app/views/projects/commit/_pipeline.html.haml19
-rw-r--r--app/views/projects/commit/_pipeline_stage.html.haml14
-rw-r--r--app/views/projects/commit/_pipelines_list.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml5
-rw-r--r--app/views/projects/pipelines/_graph.html.haml4
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml18
-rw-r--r--app/views/projects/pipelines/index.html.haml3
-rw-r--r--app/views/projects/stage/_graph.html.haml23
-rw-r--r--app/views/projects/stage/_in_stage_group.html.haml (renamed from app/views/projects/commit/_pipeline_status_group.html.haml)0
-rw-r--r--app/views/projects/stage/_stage.html.haml14
-rw-r--r--lib/gitlab/ci/status/build/cancelable.rb31
-rw-r--r--lib/gitlab/ci/status/build/common.rb19
-rw-r--r--lib/gitlab/ci/status/build/factory.rb19
-rw-r--r--lib/gitlab/ci/status/build/play.rb39
-rw-r--r--lib/gitlab/ci/status/build/retryable.rb31
-rw-r--r--lib/gitlab/ci/status/build/stop.rb47
-rw-r--r--lib/gitlab/ci/status/core.rb12
-rw-r--r--lib/gitlab/ci/status/factory.rb43
-rw-r--r--lib/gitlab/ci/status/pipeline/common.rb4
-rw-r--r--lib/gitlab/ci/status/pipeline/factory.rb30
-rw-r--r--lib/gitlab/ci/status/stage/common.rb24
-rw-r--r--lib/gitlab/ci/status/stage/factory.rb15
-rw-r--r--lib/gitlab/data_builder/pipeline.rb2
-rw-r--r--spec/factories/ci/stages.rb13
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/status/stage/common_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb37
-rw-r--r--spec/models/ci/pipeline_spec.rb55
-rw-r--r--spec/models/ci/stage_spec.rb133
-rw-r--r--spec/models/commit_status_spec.rb45
-rw-r--r--spec/models/concerns/has_status_spec.rb2
48 files changed, 724 insertions, 233 deletions
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 999d279e5b9..7ddef2cdd5f 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -4,25 +4,7 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, pipeline.sha)
end
- def ci_status_with_icon(status, target = nil)
- content = ci_icon_for_status(status) + ci_text_for_status(status)
- klass = "ci-status ci-#{status}"
-
- if target
- link_to content, target, class: klass
- else
- content_tag :span, content, class: klass
- end
- end
-
- def ci_text_for_status(status)
- if detailed_status?(status)
- status.text
- else
- status
- end
- end
-
+ # Is used by Commit and Merge Request Widget
def ci_label_for_status(status)
if detailed_status?(status)
return status.label
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 88c46076df6..73564dd2aa0 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -100,6 +100,10 @@ module Ci
end
end
+ def detailed_status
+ Gitlab::Ci::Status::Build::Factory.new(self).fabricate!
+ end
+
def manual?
self.when == 'manual'
end
@@ -123,6 +127,10 @@ module Ci
end
end
+ def cancelable?
+ active?
+ end
+
def retryable?
project.builds_enabled? && commands.present? && complete?
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index caf6908505e..fda8228a1e9 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
@@ -98,17 +96,35 @@ module Ci
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 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
+ status_sql = statuses.latest.where('stage=sg.stage').status_sql
+
+ stages_query = statuses.group('stage').select(:stage)
+ .order('max(stage_idx)')
+
+ stages_with_statuses = CommitStatus.from(stages_query, :sg).
+ pluck('sg.stage', status_sql)
+
+ stages_with_statuses.map do |stage|
+ Ci::Stage.new(self, name: stage.first, status: stage.last)
+ end
+ end
+
+ def artifacts
+ builds.latest.with_artifacts_not_expired
end
def project_id
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
new file mode 100644
index 00000000000..d2a37c0a827
--- /dev/null
+++ b/app/models/ci/stage.rb
@@ -0,0 +1,37 @@
+module Ci
+ # Currently this is artificial object, constructed dynamically
+ # We should migrate this object to actual database record in the future
+ class Stage
+ include StaticModel
+
+ attr_reader :pipeline, :name
+
+ delegate :project, to: :pipeline
+
+ def initialize(pipeline, name:, status: nil)
+ @pipeline = pipeline
+ @name = name
+ @status = status
+ end
+
+ def to_param
+ name
+ end
+
+ def status
+ @status ||= statuses.latest.status
+ end
+
+ def detailed_status
+ Gitlab::Ci::Status::Stage::Factory.new(self).fabricate!
+ end
+
+ def statuses
+ @statuses ||= pipeline.statuses.where(stage: name)
+ end
+
+ def builds
+ @builds ||= pipeline.builds.where(stage: name)
+ end
+ end
+end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index c345bf293c9..fce16174e22 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -31,18 +31,13 @@ class CommitStatus < ActiveRecord::Base
end
scope :exclude_ignored, -> do
- quoted_when = connection.quote_column_name('when')
# We want to ignore failed_but_allowed jobs
where("allow_failure = ? OR status IN (?)",
- false, all_state_names - [:failed, :canceled]).
- # We want to ignore skipped manual jobs
- where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped').
- # We want to ignore skipped on_failure
- where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped')
+ false, all_state_names - [:failed, :canceled])
end
- scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) }
- scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) }
+ scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
+ scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
state_machine :status do
event :enqueue do
@@ -117,20 +112,6 @@ class CommitStatus < ActiveRecord::Base
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end
- def self.stages
- # We group by stage name, but order stages by theirs' index
- unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
- end
-
- def self.stages_status
- # We execute subquery for each stage to calculate a stage status
- statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql)
- statuses.inject({}) do |h, k|
- h[k.first] = k.last
- h
- end
- end
-
def failed_but_allowed?
allow_failure? && (failed? || canceled?)
end
@@ -150,4 +131,8 @@ class CommitStatus < ActiveRecord::Base
def has_trace?
false
end
+
+ def detailed_status
+ Gitlab::Ci::Status::Factory.new(self).fabricate!
+ end
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 2f5aa91a964..90432fc4050 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -4,7 +4,7 @@ module HasStatus
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running]
- COMPLETED_STATUSES = %w[success failed canceled]
+ COMPLETED_STATUSES = %w[success failed canceled skipped]
ORDERED_STATUSES = %w[failed pending running canceled success skipped]
class_methods do
@@ -23,9 +23,10 @@ module HasStatus
canceled = scope.canceled.select('count(*)').to_sql
"(CASE
+ WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created'
- WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped'
+ WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 2e028c44d8b..79eb97b7b55 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -44,11 +44,11 @@ module Ci
def valid_statuses_for_when(value)
case value
when 'on_success'
- %w[success]
+ %w[success skipped]
when 'on_failure'
%w[failed]
when 'always'
- %w[success failed]
+ %w[success failed skipped]
else
[]
end
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 73038164056..fa8be25ffa8 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -91,7 +91,7 @@
%strong ##{build.id}
%td.status
- = ci_status_with_icon(build.status)
+ = render "ci/status/icon_with_label", subject: build
%td.status
- if project
diff --git a/app/views/ci/status/_icon_with_label.html.haml b/app/views/ci/status/_icon_with_label.html.haml
new file mode 100644
index 00000000000..65a74e88444
--- /dev/null
+++ b/app/views/ci/status/_icon_with_label.html.haml
@@ -0,0 +1,10 @@
+- details_path = subject.details_path if subject.has_details?(current_user)
+- klass = "ci-status ci-#{subject.status}"
+- if details_path
+ = link_to details_path, class: klass do
+ = custom_icon(status.icon)
+ = status.text
+- else
+ %span{ class: klass }
+ = custom_icon(status.icon)
+ = status.text
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 697c8d19257..56c1949ab2b 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -133,7 +133,7 @@
%tr.success-message
%td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"}
- build_count = @pipeline.statuses.latest.size
- - stage_count = @pipeline.stages.size
+ - stage_count = @pipeline.stages_count
Pipeline
%a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"}
= "\##{@pipeline.id}"
diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb
index ae22d474f2c..40e5e306426 100644
--- a/app/views/notify/pipeline_success_email.text.erb
+++ b/app/views/notify/pipeline_success_email.text.erb
@@ -16,7 +16,7 @@ Commit Author: <%= commit.author_name %>
<% end -%>
<% build_count = @pipeline.statuses.latest.size -%>
-<% stage_count = @pipeline.stages.size -%>
+<% stage_count = @pipeline.stages_count -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index f6aa20c4579..5e4e30f08d5 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,6 +1,6 @@
.content-block.build-header
.header-content
- = ci_status_with_icon(@build.status)
+ = render "ci/status/icon_with_label", subject: build
Build
%strong ##{@build.id}
in pipeline
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index d5004f6a066..ce8b66b1945 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -111,7 +111,7 @@
%span.label.label-primary
= tag
- - if @build.pipeline.stages.many?
+ - if @build.pipeline.stages_count > 1
.dropdown.build-dropdown
.title Stage
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
@@ -120,7 +120,7 @@
%ul.dropdown-menu
- @build.pipeline.stages.each do |stage|
%li
- %a.stage-item= stage
+ %a.stage-item= stage.name
.builds-container
- HasStatus::ORDERED_STATUSES.each do |build_status|
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index e75547c815f..6b0cd3e49a0 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -9,10 +9,7 @@
%tr.build.commit{class: ('retried' if retried)}
%td.status
- - if can?(current_user, :read_build, build)
- = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
- - else
- = ci_status_with_icon(build.status)
+ = render "ci/status/icon_with_label", subject: build
%td.branch-commit
- if can?(current_user, :read_build, build)
@@ -104,9 +101,9 @@
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif allow_retry
- - if build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
- = icon('repeat')
- - elsif build.playable? && !admin
+ - if build.playable? && !admin
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play')
+ - elsif build.retryable?
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = icon('repeat')
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 0f08f4e8592..84243e4306d 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -1,13 +1,10 @@
- status = pipeline.status
-- detailed_status = pipeline.detailed_status
- show_commit = local_assigns.fetch(:show_commit, true)
- show_branch = local_assigns.fetch(:show_branch, true)
%tr.commit
%td.commit-link
- = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do
- = ci_icon_for_status(detailed_status)
- = ci_text_for_status(detailed_status)
+ = render "ci/status/icon_with_label", subject: pipeline
%td
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
@@ -43,15 +40,13 @@
- else
Cant find HEAD commit for this branch
- - stages_status = pipeline.statuses.latest.stages_status
%td.stage-cell
- - stages.each do |stage|
- - status = stages_status[stage]
- - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- - if status
+ - pipeline.stages.each do |stage|
+ - if stage.status
+ - tooltip = "#{stage.name.titleize}: #{stage.status || 'not found'}"
.stage-container
- = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
- = ci_icon_for_status(status)
+ = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage.name), class: "has-tooltip ci-status-icon-#{stage.status}", title: tooltip do
+ = ci_icon_for_status(stage.status)
%td
- if pipeline.duration
diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml
deleted file mode 100644
index 3a3d750439f..00000000000
--- a/app/views/projects/commit/_ci_stage.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-%tr
- %th{colspan: 10}
- %strong
- %a{name: stage}
- - status = statuses.latest.status
- %span{class: "ci-status-link ci-status-icon-#{status}"}
- = ci_icon_for_status(status)
- - if stage
- &nbsp;
- = stage.titleize
- = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
- = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
-%tr
- %td{colspan: 10}
- &nbsp;
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 1174158eb65..c7b5c1124b3 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -24,20 +24,8 @@
in
= time_interval_in_words pipeline.duration
- .row-content-block.build-content.middle-block.pipeline-graph.hidden
- .pipeline-visualization
- %ul.stage-column-list
- - stages = pipeline.stages_with_latest_statuses
- - stages.each do |stage, statuses|
- %li.stage-column
- .stage-name
- %a{name: stage}
- - if stage
- = stage.titleize
- .builds-container
- %ul
- = render "projects/commit/pipeline_stage", statuses: statuses
-
+ .row-content-block.build-content.middle-block.hidden
+ = render "projects/pipelines/graph", pipeline: pipeline
- if pipeline.yaml_errors.present?
.bs-callout.bs-callout-danger
@@ -62,5 +50,4 @@
- if pipeline.project.build_coverage_enabled?
%th Coverage
%th
- - pipeline.statuses.relevant.stages.each do |stage|
- = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
+ = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml
deleted file mode 100644
index f9a9c8707f5..00000000000
--- a/app/views/projects/commit/_pipeline_stage.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
-- status_groups.each do |group_name, grouped_statuses|
- - if grouped_statuses.one?
- - status = grouped_statuses.first
- - is_playable = status.playable? && can?(current_user, :update_build, @project)
- %li.build{ class: ("playable" if is_playable) }
- .curve
- .build-content
- = render "projects/#{status.to_partial_path}_pipeline", subject: status
- - else
- %li.build
- .curve
- .dropdown.inline.build-content
- = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 2dc91a9b762..7f42fde0fea 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -12,4 +12,4 @@
%th Stages
%th
%th
- = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false
+ = render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 7f751d9ae2e..69cb1631ee8 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -8,10 +8,7 @@
%tr.generic_commit_status{class: ('retried' if retried)}
%td.status
- - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
- = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url)
- - else
- = ci_status_with_icon(generic_commit_status.status)
+ = render "ci/status/icon_with_label", subject: generic_commit_status
%td.generic_commit_status-link
- if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
diff --git a/app/views/projects/pipelines/_graph.html.haml b/app/views/projects/pipelines/_graph.html.haml
new file mode 100644
index 00000000000..0202833c0bf
--- /dev/null
+++ b/app/views/projects/pipelines/_graph.html.haml
@@ -0,0 +1,4 @@
+- pipeline = local_assigns.fetch(:pipeline)
+.pipeline-visualization.pipeline-graph
+ %ul.stage-column-list
+ = render partial: "projects/stage/graph", collection: pipeline.stages, as: :stage
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 229bdfb0e8d..f7385184a2b 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -1,6 +1,6 @@
.page-content-header
.header-main-content
- = ci_status_with_icon(@pipeline.detailed_status)
+ = render "ci/status/icon_with_label", subject: @pipeline
%strong Pipeline ##{@commit.pipelines.last.id}
triggered #{time_ago_with_tooltip(@commit.authored_date)} by
= author_avatar(@commit, size: 24)
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 3464e155a1b..739e5930822 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -12,19 +12,8 @@
.tab-content
#js-tab-pipeline.tab-pane
- .build-content.middle-block.pipeline-graph
- .pipeline-visualization
- %ul.stage-column-list
- - stages = pipeline.stages_with_latest_statuses
- - stages.each do |stage, statuses|
- %li.stage-column
- .stage-name
- %a{name: stage}
- - if stage
- = stage.titleize
- .builds-container
- %ul
- = render "projects/commit/pipeline_stage", statuses: statuses
+ .build-content.middle-block
+ = render "projects/pipelines/graph", pipeline: pipeline
#js-tab-builds.tab-pane
- if pipeline.yaml_errors.present?
@@ -50,5 +39,4 @@
- if pipeline.project.build_coverage_enabled?
%th Coverage
%th
- - pipeline.statuses.relevant.stages.each do |stage|
- = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
+ = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 4bc49072f35..e1e787dbde4 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -37,7 +37,6 @@
%span CI Lint
%div.content-list.pipelines
- - stages = @pipelines.stages
- if @pipelines.blank?
%div
.nothing-here-block No pipelines to show
@@ -51,6 +50,6 @@
%th Stages
%th
%th.hidden-xs
- = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
+ = render @pipelines, commit_sha: true, stage: true, allow_retry: true
= paginate @pipelines, theme: 'gitlab'
diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml
new file mode 100644
index 00000000000..d8c87fae5a1
--- /dev/null
+++ b/app/views/projects/stage/_graph.html.haml
@@ -0,0 +1,23 @@
+- stage = local_assigns.fetch(:stage)
+- statuses = stage.statuses.latest
+- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
+%li.stage-column
+ .stage-name
+ %a{ name: stage.name }
+ - if stage.name
+ = stage.name.titleize
+ .builds-container
+ %ul
+ - status_groups.each do |group_name, grouped_statuses|
+ - if grouped_statuses.one?
+ - status = grouped_statuses.first
+ - is_playable = status.playable? && can?(current_user, :update_build, @project)
+ %li.build{ class: ("playable" if is_playable) }
+ .curve
+ .build-content
+ = render "projects/#{status.to_partial_path}_pipeline", subject: status
+ - else
+ %li.build
+ .curve
+ .dropdown.inline.build-content
+ = render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses
diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml
index 2d198d1b389..2d198d1b389 100644
--- a/app/views/projects/commit/_pipeline_status_group.html.haml
+++ b/app/views/projects/stage/_in_stage_group.html.haml
diff --git a/app/views/projects/stage/_stage.html.haml b/app/views/projects/stage/_stage.html.haml
new file mode 100644
index 00000000000..1eca375db3d
--- /dev/null
+++ b/app/views/projects/stage/_stage.html.haml
@@ -0,0 +1,14 @@
+%tr
+ %th{colspan: 10}
+ %strong
+ %a{ name: stage.name }
+ %span{class: "ci-status-link ci-status-icon-#{stage.status}"}
+ = ci_icon_for_status(stage.status)
+ - if stage.name
+ &nbsp;
+ = stage.name.titleize
+= render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true
+= render stage.statuses.retried_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true
+%tr
+ %td{colspan: 10}
+ &nbsp;
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
new file mode 100644
index 00000000000..bff0464ef0c
--- /dev/null
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module Ci
+ module Status
+ module Status
+ class Cancelable < SimpleDelegator
+ extend Status::Extended
+
+ def has_action?(current_user)
+ can?(current_user, :update_build, subject)
+ end
+
+ def action_icon
+ 'remove'
+ end
+
+ def action_path
+ cancel_namespace_project_build_path(subject.project.namespace, subject.project, subject)
+ end
+
+ def action_method
+ :post
+ end
+
+ def self.matches?(build)
+ build.cancelable?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
new file mode 100644
index 00000000000..2fb79afa3d3
--- /dev/null
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ module Common
+ def has_details?(current_user)
+ can?(current_user, :read_build, subject)
+ end
+
+ def details_path
+ namespace_project_build_path(@subject.project.namespace,
+ @subject.project,
+ @subject.pipeline)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
new file mode 100644
index 00000000000..8f420a93954
--- /dev/null
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Factory < Status::Factory
+ private
+
+ def extended_statuses
+ [Stop, Play, Cancelable, Retryable]
+ end
+
+ def core_status
+ super.extend(Status::Build::Common)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
new file mode 100644
index 00000000000..d295850137b
--- /dev/null
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module Ci
+ module Status
+ module Status
+ class Play < SimpleDelegator
+ extend Status::Extended
+
+ def text
+ 'play'
+ end
+
+ def label
+ 'play'
+ end
+
+ def has_action?(current_user)
+ can?(current_user, :update_build, subject)
+ end
+
+ def action_icon
+ 'play'
+ end
+
+ def action_path
+ play_namespace_project_build_path(subject.project.namespace, subject.project, subject)
+ end
+
+ def action_method
+ :post
+ end
+
+ def self.matches?(build)
+ build.playable? && !build.stops_environment?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
new file mode 100644
index 00000000000..b3c0eedadf8
--- /dev/null
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module Ci
+ module Status
+ module Status
+ class Retryable < SimpleDelegator
+ extend Status::Extended
+
+ def has_action?(current_user)
+ can?(current_user, :update_build, subject)
+ end
+
+ def action_icon
+ 'repeat'
+ end
+
+ def action_path
+ retry_namespace_project_build_path(subject.project.namespace, subject.project, subject)
+ end
+
+ def action_method
+ :post
+ end
+
+ def self.matches?(build)
+ build.retryable?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
new file mode 100644
index 00000000000..261de9695c5
--- /dev/null
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -0,0 +1,47 @@
+module Gitlab
+ module Ci
+ module Status
+ module Status
+ class Play < SimpleDelegator
+ extend Status::Extended
+
+ def text
+ 'stop'
+ end
+
+ def label
+ 'stop'
+ end
+
+ def icon
+ 'icon_status_skipped'
+ end
+
+ def to_s
+ 'stop'
+ end
+
+ def has_action?(current_user)
+ can?(current_user, :update_build, subject)
+ end
+
+ def action_icon
+ :play
+ end
+
+ def action_path
+ play_namespace_project_build_path(subject.project.namespace, subject.project, subject)
+ end
+
+ def action_method
+ :post
+ end
+
+ def self.matches?(build)
+ build.playable? && build.stops_environment?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb
index ce4108fdcf2..6b47096f811 100644
--- a/lib/gitlab/ci/status/core.rb
+++ b/lib/gitlab/ci/status/core.rb
@@ -33,16 +33,16 @@ module Gitlab
self.class.name.demodulize.downcase.underscore
end
- def has_details?
- raise NotImplementedError
+ def has_details?(_user = nil)
+ false
end
def details_path
raise NotImplementedError
end
- def has_action?
- raise NotImplementedError
+ def has_action?(_user = nil)
+ false
end
def action_icon
@@ -52,6 +52,10 @@ module Gitlab
def action_path
raise NotImplementedError
end
+
+ def action_method
+ raise NotImplementedError
+ end
end
end
end
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
new file mode 100644
index 00000000000..b2f896f2211
--- /dev/null
+++ b/lib/gitlab/ci/status/factory.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module Ci
+ module Status
+ class Factory
+ attr_reader :subject
+
+ def initialize(subject)
+ @subject = subject
+ end
+
+ def fabricate!
+ if extended_status
+ extended_status.new(core_status)
+ else
+ core_status
+ end
+ end
+
+ private
+
+ def subject_status
+ @subject_status ||= subject.status
+ end
+
+ def core_status
+ Gitlab::Ci::Status
+ .const_get(subject_status.capitalize)
+ .new(subject)
+ end
+
+ def extended_status
+ @extended ||= extended_statuses.find do |status|
+ status.matches?(subject)
+ end
+ end
+
+ def extended_statuses
+ []
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb
index 25e52bec3da..5f79044a496 100644
--- a/lib/gitlab/ci/status/pipeline/common.rb
+++ b/lib/gitlab/ci/status/pipeline/common.rb
@@ -3,8 +3,8 @@ module Gitlab
module Status
module Pipeline
module Common
- def has_details?
- true
+ def has_details?(current_user)
+ can?(current_user, :read_pipeline, subject)
end
def details_path
diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb
index 71d27bf7cf5..4ac4ec671d0 100644
--- a/lib/gitlab/ci/status/pipeline/factory.rb
+++ b/lib/gitlab/ci/status/pipeline/factory.rb
@@ -2,35 +2,15 @@ module Gitlab
module Ci
module Status
module Pipeline
- class Factory
- EXTENDED_STATUSES = [Pipeline::SuccessWithWarnings]
-
- def initialize(pipeline)
- @pipeline = pipeline
- @status = pipeline.status || :created
- end
-
- def fabricate!
- if extended_status
- extended_status.new(core_status)
- else
- core_status
- end
- end
-
+ class Factory < Status::Factory
private
- def core_status
- Gitlab::Ci::Status
- .const_get(@status.capitalize)
- .new(@pipeline)
- .extend(Status::Pipeline::Common)
+ def extended_statuses
+ [Pipeline::SuccessWithWarnings]
end
- def extended_status
- @extended ||= EXTENDED_STATUSES.find do |status|
- status.matches?(@pipeline)
- end
+ def core_status
+ super.extend(Status::Pipeline::Common)
end
end
end
diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb
new file mode 100644
index 00000000000..e6ee2f92341
--- /dev/null
+++ b/lib/gitlab/ci/status/stage/common.rb
@@ -0,0 +1,24 @@
+module Gitlab
+ module Ci
+ module Status
+ module Stage
+ module Common
+ def has_details?(current_user)
+ can?(current_user, :read_pipeline, subject)
+ end
+
+ def details_path
+ namespace_project_pipeline_path(@subject.project.namespace,
+ @subject.project,
+ @subject.pipeline,
+ anchor: @subject.name)
+ end
+
+ def has_action?
+ false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/stage/factory.rb b/lib/gitlab/ci/status/stage/factory.rb
new file mode 100644
index 00000000000..c6522d5ada1
--- /dev/null
+++ b/lib/gitlab/ci/status/stage/factory.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ module Ci
+ module Status
+ module Stage
+ class Factory < Status::Factory
+ private
+
+ def core_status
+ super.extend(Status::Stage::Common)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index 06a783ebc1c..e50e54b6e99 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -22,7 +22,7 @@ module Gitlab
sha: pipeline.sha,
before_sha: pipeline.before_sha,
status: pipeline.status,
- stages: pipeline.stages,
+ stages: pipeline.stages_name,
created_at: pipeline.created_at,
finished_at: pipeline.finished_at,
duration: pipeline.duration
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
new file mode 100644
index 00000000000..ee3b17b8bf1
--- /dev/null
+++ b/spec/factories/ci/stages.rb
@@ -0,0 +1,13 @@
+FactoryGirl.define do
+ factory :ci_stage, class: Ci::Stage do
+ transient do
+ name 'test'
+ status nil
+ pipeline factory: :ci_empty_pipeline
+ end
+
+ initialize_with do
+ Ci::Stage.new(pipeline, name: name, status: status)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
new file mode 100644
index 00000000000..d5bd7f7102b
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Factory do
+ subject do
+ described_class.new(object)
+ end
+
+ let(:status) { subject.fabricate! }
+
+ context 'when object has a core status' do
+ HasStatus::AVAILABLE_STATUSES.each do |core_status|
+ context "when core status is #{core_status}" do
+ let(:object) { double(status: core_status) }
+
+ it "fabricates a core status #{core_status}" do
+ expect(status).to be_a(
+ Gitlab::Ci::Status.const_get(core_status.capitalize))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb
new file mode 100644
index 00000000000..f3259c6f23e
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Stage::Common do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+ let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
+
+ subject do
+ Class.new(Gitlab::Ci::Status::Core)
+ .new(stage).extend(described_class)
+ end
+
+ it 'does not have action' do
+ expect(subject).not_to have_action
+ end
+
+ it 'has details' do
+ expect(subject).to have_details
+ end
+
+ it 'links to the pipeline details page' do
+ expect(subject.details_path)
+ .to include "pipelines/#{pipeline.id}"
+ expect(subject.details_path)
+ .to include "##{stage.name}"
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
new file mode 100644
index 00000000000..17929665c83
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Stage::Factory do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+ let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
+
+ subject do
+ described_class.new(stage)
+ end
+
+ let(:status) do
+ subject.fabricate!
+ end
+
+ context 'when stage has a core status' do
+ HasStatus::AVAILABLE_STATUSES.each do |core_status|
+ context "when core status is #{core_status}" do
+ before do
+ create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
+ create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status)
+ create(:ci_build, pipeline: pipeline, stage: 'build', status: :failed)
+ end
+
+ it "fabricates a core status #{core_status}" do
+ expect(status).to be_a(
+ Gitlab::Ci::Status.const_get(core_status.capitalize))
+ end
+
+ it 'extends core status with common stage methods' do
+ expect(status).to have_details
+ expect(status.details_path).to include "pipelines/#{pipeline.id}"
+ expect(status.details_path).to include "##{stage.name}"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 3f93d9ddf19..8158e71dd55 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -20,8 +20,6 @@ describe Ci::Pipeline, models: true do
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
- it { is_expected.to delegate_method(:stages).to(:statuses) }
-
describe '#valid_commit_sha' do
context 'commit.sha can not start with 00000000' do
before do
@@ -125,16 +123,55 @@ describe Ci::Pipeline, models: true do
end
describe '#stages' do
- let(:pipeline2) { FactoryGirl.create :ci_pipeline, project: project }
- subject { CommitStatus.where(pipeline: [pipeline, pipeline2]).stages }
-
before do
- FactoryGirl.create :ci_build, pipeline: pipeline2, stage: 'test', stage_idx: 1
- FactoryGirl.create :ci_build, pipeline: pipeline, stage: 'build', stage_idx: 0
+ create(:commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success')
+ create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed')
+ create(:commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running')
+ create(:commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success')
+ end
+
+ subject { pipeline.stages }
+
+ context 'stages list' do
+ it 'returns ordered list of stages' do
+ expect(subject.map(&:name)).to eq(%w[build test deploy])
+ end
+ end
+
+ it 'returns a valid number of stages' do
+ expect(pipeline.stages_count).to eq(3)
+ end
+
+ it 'returns a valid names of stages' do
+ expect(pipeline.stages_name).to eq(['build', 'test', 'deploy'])
end
- it 'return all stages' do
- is_expected.to eq(%w(build test))
+ context 'stages with statuses' do
+ let(:statuses) do
+ subject.map do |stage|
+ [stage.name, stage.status]
+ end
+ end
+
+ it 'returns list of stages with statuses' do
+ expect(statuses).to eq([['build', 'failed'],
+ ['test', 'success'],
+ ['deploy', 'running']
+ ])
+ end
+
+ context 'when build is retried' do
+ before do
+ create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success')
+ end
+
+ it 'ignores the previous state' do
+ expect(statuses).to eq([['build', 'success'],
+ ['test', 'success'],
+ ['deploy', 'running']
+ ])
+ end
+ end
end
end
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
new file mode 100644
index 00000000000..f232761dba2
--- /dev/null
+++ b/spec/models/ci/stage_spec.rb
@@ -0,0 +1,133 @@
+require 'spec_helper'
+
+describe Ci::Stage, models: true do
+ let(:stage) { build(:ci_stage) }
+ let(:pipeline) { stage.pipeline }
+ let(:stage_name) { stage.name }
+
+ describe '#expectations' do
+ subject { stage }
+
+ it { is_expected.to include_module(StaticModel) }
+
+ it { is_expected.to respond_to(:pipeline) }
+ it { is_expected.to respond_to(:name) }
+
+ it { is_expected.to delegate_method(:project).to(:pipeline) }
+ end
+
+ describe '#statuses' do
+ let!(:stage_build) { create_job(:ci_build) }
+ let!(:commit_status) { create_job(:commit_status) }
+ let!(:other_build) { create_job(:ci_build, stage: 'other stage') }
+
+ subject { stage.statuses }
+
+ it "returns only matching statuses" do
+ is_expected.to contain_exactly(stage_build, commit_status)
+ end
+ end
+
+ describe '#builds' do
+ let!(:stage_build) { create_job(:ci_build) }
+ let!(:commit_status) { create_job(:commit_status) }
+
+ subject { stage.builds }
+
+ it "returns only builds" do
+ is_expected.to contain_exactly(stage_build)
+ end
+ end
+
+ describe '#status' do
+ subject { stage.status }
+
+ context 'if status is already defined' do
+ let(:stage) { build(:ci_stage, status: 'success') }
+
+ it "returns defined status" do
+ is_expected.to eq('success')
+ end
+ end
+
+ context 'if status has to be calculated' do
+ let!(:stage_build) { create_job(:ci_build, status: :failed) }
+
+ it "returns status of a build" do
+ is_expected.to eq('failed')
+ end
+
+ context 'and builds are retried' do
+ let!(:new_build) { create_job(:ci_build, status: :success) }
+
+ it "returns status of latest build" do
+ is_expected.to eq('success')
+ end
+ end
+ end
+ end
+
+ describe '#detailed_status' do
+ subject { stage.detailed_status }
+
+ context 'when build is created' do
+ let!(:stage_build) { create_job(:ci_build, status: :created) }
+
+ it 'returns detailed status for created stage' do
+ expect(subject.text).to eq 'created'
+ end
+ end
+
+ context 'when build is pending' do
+ let!(:stage_build) { create_job(:ci_build, status: :pending) }
+
+ it 'returns detailed status for pending stage' do
+ expect(subject.text).to eq 'pending'
+ end
+ end
+
+ context 'when build is running' do
+ let!(:stage_build) { create_job(:ci_build, status: :running) }
+
+ it 'returns detailed status for running stage' do
+ expect(subject.text).to eq 'running'
+ end
+ end
+
+ context 'when build is successful' do
+ let!(:stage_build) { create_job(:ci_build, status: :success) }
+
+ it 'returns detailed status for successful stage' do
+ expect(subject.text).to eq 'passed'
+ end
+ end
+
+ context 'when build is failed' do
+ let!(:stage_build) { create_job(:ci_build, status: :failed) }
+
+ it 'returns detailed status for failed stage' do
+ expect(subject.text).to eq 'failed'
+ end
+ end
+
+ context 'when build is canceled' do
+ let!(:stage_build) { create_job(:ci_build, status: :canceled) }
+
+ it 'returns detailed status for canceled stage' do
+ expect(subject.text).to eq 'canceled'
+ end
+ end
+
+ context 'when build is skipped' do
+ let!(:stage_build) { create_job(:ci_build, status: :skipped) }
+
+ it 'returns detailed status for skipped stage' do
+ expect(subject.text).to eq 'skipped'
+ end
+ end
+ end
+
+ def create_job(type, status: 'success', stage: stage_name)
+ create(type, pipeline: pipeline, stage: stage, status: status)
+ end
+end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 80c2a1bc7a9..1ec08c2a9d0 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -175,7 +175,7 @@ describe CommitStatus, models: true do
end
it 'returns statuses without what we want to ignore' do
- is_expected.to eq(statuses.values_at(1, 2, 4, 5, 6, 8, 9))
+ is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9))
end
end
@@ -200,49 +200,6 @@ describe CommitStatus, models: true do
end
end
- describe '#stages' do
- before do
- create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success'
- create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed'
- create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running'
- create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success'
- end
-
- context 'stages list' do
- subject { CommitStatus.where(pipeline: pipeline).stages }
-
- it 'returns ordered list of stages' do
- is_expected.to eq(%w[build test deploy])
- end
- end
-
- context 'stages with statuses' do
- subject { CommitStatus.where(pipeline: pipeline).latest.stages_status }
-
- it 'returns list of stages with statuses' do
- is_expected.to eq({
- 'build' => 'failed',
- 'test' => 'success',
- 'deploy' => 'running'
- })
- end
-
- context 'when build is retried' do
- before do
- create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success'
- end
-
- it 'ignores a previous state' do
- is_expected.to eq({
- 'build' => 'success',
- 'test' => 'success',
- 'deploy' => 'running'
- })
- end
- end
- end
- end
-
describe '#commit' do
it 'returns commit pipeline has been created for' do
expect(commit_status.commit).to eq project.commit
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 9defb17dc92..4d0f51fe82a 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -48,7 +48,7 @@ describe HasStatus do
[create(type, status: :failed, allow_failure: true)]
end
- it { is_expected.to eq 'success' }
+ it { is_expected.to eq 'skipped' }
end
context 'success and canceled' do