Merge branch 'pipeline-stage' into 'master'
Refine pipeline stages ## What does this MR do? Introduces a concept of `Ci::Stage` to make it easier to have detailed statuses. ## Why was this MR needed? This is needed to simplify the handling of `Ci::Statuses` and make the `Stage` actual concept in code: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7889 See merge request !7927
This commit is contained in:
commit
10d4b20ad1
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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 %>.
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -104,9 +104,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')
|
||||
|
|
|
@ -43,15 +43,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
|
||||
|
|
|
@ -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
|
||||
|
||||
= 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}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
- 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 }
|
||||
= 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
|
|
@ -0,0 +1,13 @@
|
|||
%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)
|
||||
|
||||
= 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}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
module Gitlab
|
||||
module Ci
|
||||
module Status
|
||||
module Stage
|
||||
module Common
|
||||
def has_details?
|
||||
true
|
||||
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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
it 'return all stages' do
|
||||
is_expected.to eq(%w(build test))
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue