Commit fc57b314 authored by Grzegorz Bizon's avatar Grzegorz Bizon

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
  ...
parents f7335aa5 65f32060
......@@ -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
......
......@@ -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
......
......@@ -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
......
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
......@@ -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
......@@ -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
......
......@@ -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
......
- 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
......@@ -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 %>.
......
.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
......
......@@ -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|
......
......@@ -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')
- 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
......
%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;
......@@ -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
- 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
......@@ -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
......
- pipeline = local_assigns.fetch(:pipeline)
.pipeline-visualization.pipeline-graph
%ul.stage-column-list
= render partial: "projects/stage/graph", collection: pipeline.stages, as: :stage
.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)
......
......@@ -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'
- 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
%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;
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
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
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
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
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
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
......@@ -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
......
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
......@@ -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
......
......@@ -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
......
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
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
......
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
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
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
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
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
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
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: 'mac', stage_idx: 0, status: 'success')
end
it 'return all stages' do
is_expected.to eq(%w(build test))
it 'ignores the previous state' do
expect(statuses).to eq([['build', 'success'],
['test', 'success'],
['deploy', 'running']
])
end
end
end
end
......
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment