Merge branch 'build-statuses' into 'master'

Detailed build statuses

## What does this MR do?

Implements detailed statuses for `Ci::Builds` and `CommitStatus`.

It also adds a new icon for manual build.

## Screenshots

![manual_builds_icon](/uploads/22b5c594350856c85398ef705a635f8b/manual_builds_icon.png)

## What are the relevant issue numbers?

See #24273, closes #22642

See merge request !7989
This commit is contained in:
Rémy Coutable 2016-12-14 12:34:22 +00:00
commit 3e90aa1119
58 changed files with 1126 additions and 171 deletions

View File

@ -4,25 +4,7 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, pipeline.sha) builds_namespace_project_commit_path(project.namespace, project, pipeline.sha)
end end
def ci_status_with_icon(status, target = nil) # Is used by Commit and Merge Request Widget
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
def ci_label_for_status(status) def ci_label_for_status(status)
if detailed_status?(status) if detailed_status?(status)
return status.label return status.label

View File

@ -100,6 +100,12 @@ module Ci
end end
end end
def detailed_status(current_user)
Gitlab::Ci::Status::Build::Factory
.new(self, current_user)
.fabricate!
end
def manual? def manual?
self.when == 'manual' self.when == 'manual'
end end
@ -123,8 +129,13 @@ module Ci
end end
end end
def cancelable?
active?
end
def retryable? def retryable?
project.builds_enabled? && commands.present? && complete? project.builds_enabled? && commands.present? &&
(success? || failed? || canceled?)
end end
def retried? def retried?
@ -148,7 +159,7 @@ module Ci
end end
def environment_action def environment_action
self.options.fetch(:environment, {}).fetch(:action, 'start') self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options
end end
def outdated_deployment? def outdated_deployment?

View File

@ -336,8 +336,10 @@ module Ci
.select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
end end
def detailed_status def detailed_status(current_user)
Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate! Gitlab::Ci::Status::Pipeline::Factory
.new(self, current_user)
.fabricate!
end end
private private

View File

@ -22,8 +22,10 @@ module Ci
@status ||= statuses.latest.status @status ||= statuses.latest.status
end end
def detailed_status def detailed_status(current_user)
Gitlab::Ci::Status::Stage::Factory.new(self).fabricate! Gitlab::Ci::Status::Stage::Factory
.new(self, current_user)
.fabricate!
end end
def statuses def statuses

View File

@ -131,4 +131,10 @@ class CommitStatus < ActiveRecord::Base
def has_trace? def has_trace?
false false
end end
def detailed_status(current_user)
Gitlab::Ci::Status::Factory
.new(self, current_user)
.fabricate!
end
end end

View File

@ -91,7 +91,7 @@
%strong ##{build.id} %strong ##{build.id}
%td.status %td.status
= ci_status_with_icon(build.status) = render 'ci/status/badge', status: build.detailed_status(current_user)
%td.status %td.status
- if project - if project

View File

@ -0,0 +1,10 @@
- status = local_assigns.fetch(:status)
- if status.has_details?
= link_to status.details_path, class: "ci-status ci-#{status}" do
= custom_icon(status.icon)
= status.text
- else
%span{ class: "ci-status ci-#{status}" }
= custom_icon(status.icon)
= status.text

View File

@ -1,6 +1,6 @@
.content-block.build-header .content-block.build-header
.header-content .header-content
= ci_status_with_icon(@build.status) = render 'ci/status/badge', status: @build.detailed_status(current_user)
Build Build
%strong ##{@build.id} %strong ##{@build.id}
in pipeline in pipeline

View File

@ -9,10 +9,7 @@
%tr.build.commit{class: ('retried' if retried)} %tr.build.commit{class: ('retried' if retried)}
%td.status %td.status
- if can?(current_user, :read_build, build) = render "ci/status/badge", status: build.detailed_status(current_user)
= ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
- else
= ci_status_with_icon(build.status)
%td.branch-commit %td.branch-commit
- if can?(current_user, :read_build, build) - if can?(current_user, :read_build, build)

View File

@ -1,13 +1,10 @@
- status = pipeline.status - status = pipeline.status
- detailed_status = pipeline.detailed_status
- show_commit = local_assigns.fetch(:show_commit, true) - show_commit = local_assigns.fetch(:show_commit, true)
- show_branch = local_assigns.fetch(:show_branch, true) - show_branch = local_assigns.fetch(:show_branch, true)
%tr.commit %tr.commit
%td.commit-link %td.commit-link
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do = render 'ci/status/badge', status: pipeline.detailed_status(current_user)
= ci_icon_for_status(detailed_status)
= ci_text_for_status(detailed_status)
%td %td
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do

View File

@ -8,10 +8,7 @@
%tr.generic_commit_status{class: ('retried' if retried)} %tr.generic_commit_status{class: ('retried' if retried)}
%td.status %td.status
- if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url = render 'ci/status/badge', status: generic_commit_status.detailed_status(current_user)
= ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url)
- else
= ci_status_with_icon(generic_commit_status.status)
%td.generic_commit_status-link %td.generic_commit_status-link
- if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url

View File

@ -1,6 +1,6 @@
.page-content-header .page-content-header
.header-main-content .header-main-content
= ci_status_with_icon(@pipeline.detailed_status) = render 'ci/status/badge', status: @pipeline.detailed_status(current_user)
%strong Pipeline ##{@commit.pipelines.last.id} %strong Pipeline ##{@commit.pipelines.last.id}
triggered #{time_ago_with_tooltip(@commit.authored_date)} by triggered #{time_ago_with_tooltip(@commit.authored_date)} by
= author_avatar(@commit, size: 24) = author_avatar(@commit, size: 24)

View File

@ -19,4 +19,4 @@
%li.build %li.build
.curve .curve
.dropdown.inline.build-content .dropdown.inline.build-content
= render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses

View File

@ -0,0 +1 @@
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M10.5 7.63V6.37l-.787-.13c-.044-.175-.132-.349-.263-.61l.481-.652-.918-.913-.657.478a2.346 2.346 0 0 0-.612-.26L7.656 3.5H6.388l-.132.783c-.219.043-.394.13-.612.26l-.657-.478-.918.913.437.652c-.131.218-.175.392-.262.61l-.744.086v1.261l.787.13c.044.218.132.392.263.61l-.438.651.92.913.655-.434c.175.086.394.173.613.26l.131.783h1.313l.131-.783c.219-.043.394-.13.613-.26l.656.478.918-.913-.48-.652c.13-.218.218-.435.262-.61l.656-.13zM7 8.283a1.285 1.285 0 0 1-1.313-1.305c0-.739.57-1.304 1.313-1.304.744 0 1.313.565 1.313 1.304 0 .74-.57 1.305-1.313 1.305z"/></g></svg>

After

Width:  |  Height:  |  Size: 787 B

7
lib/gitlab/allowable.rb Normal file
View File

@ -0,0 +1,7 @@
module Gitlab
module Allowable
def can?(user, action, subject)
Ability.allowed?(user, action, subject)
end
end
end

View File

@ -0,0 +1,37 @@
module Gitlab
module Ci
module Status
module Build
class Cancelable < SimpleDelegator
include Status::Extended
def has_action?
can?(user, :update_build, subject)
end
def action_icon
'ban'
end
def action_path
cancel_namespace_project_build_path(subject.project.namespace,
subject.project,
subject)
end
def action_method
:post
end
def action_title
'Cancel'
end
def self.matches?(build, user)
build.cancelable?
end
end
end
end
end
end

View File

@ -0,0 +1,19 @@
module Gitlab
module Ci
module Status
module Build
module Common
def has_details?
can?(user, :read_build, subject)
end
def details_path
namespace_project_build_path(subject.project.namespace,
subject.project,
subject)
end
end
end
end
end
end

View File

@ -0,0 +1,18 @@
module Gitlab
module Ci
module Status
module Build
class Factory < Status::Factory
def self.extended_statuses
[Status::Build::Stop, Status::Build::Play,
Status::Build::Cancelable, Status::Build::Retryable]
end
def self.common_helpers
Status::Build::Common
end
end
end
end
end
end

View File

@ -0,0 +1,53 @@
module Gitlab
module Ci
module Status
module Build
class Play < SimpleDelegator
include Status::Extended
def text
'manual'
end
def label
'manual play action'
end
def icon
'icon_status_manual'
end
def has_action?
can?(user, :update_build, subject)
end
def action_icon
'play'
end
def action_title
'Play'
end
def action_class
'ci-play-icon'
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, user)
build.playable? && !build.stops_environment?
end
end
end
end
end
end

View File

@ -0,0 +1,37 @@
module Gitlab
module Ci
module Status
module Build
class Retryable < SimpleDelegator
include Status::Extended
def has_action?
can?(user, :update_build, subject)
end
def action_icon
'refresh'
end
def action_title
'Retry'
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, user)
build.retryable?
end
end
end
end
end
end

View File

@ -0,0 +1,49 @@
module Gitlab
module Ci
module Status
module Build
class Stop < SimpleDelegator
include Status::Extended
def text
'manual'
end
def label
'manual stop action'
end
def icon
'icon_status_manual'
end
def has_action?
can?(user, :update_build, subject)
end
def action_icon
'stop'
end
def action_title
'Stop'
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, user)
build.playable? && build.stops_environment?
end
end
end
end
end
end

View File

@ -4,10 +4,14 @@ module Gitlab
# Base abstract class fore core status # Base abstract class fore core status
# #
class Core class Core
include Gitlab::Routing.url_helpers include Gitlab::Routing
include Gitlab::Allowable
def initialize(subject) attr_reader :subject, :user
def initialize(subject, user)
@subject = subject @subject = subject
@user = user
end end
def icon def icon
@ -18,10 +22,6 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
def title
"#{@subject.class.name.demodulize}: #{label}"
end
# Deprecation warning: this method is here because we need to maintain # Deprecation warning: this method is here because we need to maintain
# backwards compatibility with legacy statuses. We often do something # backwards compatibility with legacy statuses. We often do something
# like "ci-status ci-status-#{status}" to set CSS class. # like "ci-status ci-status-#{status}" to set CSS class.
@ -34,7 +34,7 @@ module Gitlab
end end
def has_details? def has_details?
raise NotImplementedError false
end end
def details_path def details_path
@ -42,16 +42,27 @@ module Gitlab
end end
def has_action? def has_action?
raise NotImplementedError false
end end
def action_icon def action_icon
raise NotImplementedError raise NotImplementedError
end end
def action_class
end
def action_path def action_path
raise NotImplementedError raise NotImplementedError
end end
def action_method
raise NotImplementedError
end
def action_title
raise NotImplementedError
end
end end
end end
end end

View File

@ -2,8 +2,12 @@ module Gitlab
module Ci module Ci
module Status module Status
module Extended module Extended
def matches?(_subject) extend ActiveSupport::Concern
raise NotImplementedError
class_methods do
def matches?(_subject, _user)
raise NotImplementedError
end
end end
end end
end end

View File

@ -2,10 +2,9 @@ module Gitlab
module Ci module Ci
module Status module Status
class Factory class Factory
attr_reader :subject def initialize(subject, user)
def initialize(subject)
@subject = subject @subject = subject
@user = user
end end
def fabricate! def fabricate!
@ -16,27 +15,32 @@ module Gitlab
end end
end end
def self.extended_statuses
[]
end
def self.common_helpers
Module.new
end
private private
def subject_status def simple_status
@subject_status ||= subject.status @simple_status ||= @subject.status || :created
end end
def core_status def core_status
Gitlab::Ci::Status Gitlab::Ci::Status
.const_get(subject_status.capitalize) .const_get(simple_status.capitalize)
.new(subject) .new(@subject, @user)
.extend(self.class.common_helpers)
end end
def extended_status def extended_status
@extended ||= extended_statuses.find do |status| @extended ||= self.class.extended_statuses.find do |status|
status.matches?(subject) status.matches?(@subject, @user)
end end
end end
def extended_statuses
[]
end
end end
end end
end end

View File

@ -4,13 +4,13 @@ module Gitlab
module Pipeline module Pipeline
module Common module Common
def has_details? def has_details?
true can?(user, :read_pipeline, subject)
end end
def details_path def details_path
namespace_project_pipeline_path(@subject.project.namespace, namespace_project_pipeline_path(subject.project.namespace,
@subject.project, subject.project,
@subject) subject)
end end
def has_action? def has_action?

View File

@ -3,14 +3,12 @@ module Gitlab
module Status module Status
module Pipeline module Pipeline
class Factory < Status::Factory class Factory < Status::Factory
private def self.extended_statuses
def extended_statuses
[Pipeline::SuccessWithWarnings] [Pipeline::SuccessWithWarnings]
end end
def core_status def self.common_helpers
super.extend(Status::Pipeline::Common) Status::Pipeline::Common
end end
end end
end end

View File

@ -3,7 +3,7 @@ module Gitlab
module Status module Status
module Pipeline module Pipeline
class SuccessWithWarnings < SimpleDelegator class SuccessWithWarnings < SimpleDelegator
extend Status::Extended include Status::Extended
def text def text
'passed' 'passed'
@ -21,7 +21,7 @@ module Gitlab
'success_with_warnings' 'success_with_warnings'
end end
def self.matches?(pipeline) def self.matches?(pipeline, user)
pipeline.success? && pipeline.has_warnings? pipeline.success? && pipeline.has_warnings?
end end
end end

View File

@ -4,14 +4,14 @@ module Gitlab
module Stage module Stage
module Common module Common
def has_details? def has_details?
true can?(user, :read_pipeline, subject.pipeline)
end end
def details_path def details_path
namespace_project_pipeline_path(@subject.project.namespace, namespace_project_pipeline_path(subject.project.namespace,
@subject.project, subject.project,
@subject.pipeline, subject.pipeline,
anchor: @subject.name) anchor: subject.name)
end end
def has_action? def has_action?

View File

@ -3,10 +3,8 @@ module Gitlab
module Status module Status
module Stage module Stage
class Factory < Status::Factory class Factory < Status::Factory
private def self.common_helpers
Status::Stage::Common
def core_status
super.extend(Status::Stage::Common)
end end
end end
end end

View File

@ -1,5 +1,11 @@
module Gitlab module Gitlab
module Routing module Routing
extend ActiveSupport::Concern
included do
include Gitlab::Routing.url_helpers
end
# Returns the URL helpers Module. # Returns the URL helpers Module.
# #
# This method caches the output as Rails' "url_helpers" method creates an # This method caches the output as Rails' "url_helpers" method creates an

View File

@ -12,12 +12,14 @@ FactoryGirl.define do
started_at 'Di 29. Okt 09:51:28 CET 2013' started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013' finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a' commands 'ls -a'
options do options do
{ {
image: "ruby:2.1", image: "ruby:2.1",
services: ["postgres"] services: ["postgres"]
} }
end end
yaml_variables do yaml_variables do
[ [
{ key: :DB_NAME, value: 'postgres', public: true } { key: :DB_NAME, value: 'postgres', public: true }
@ -60,15 +62,20 @@ FactoryGirl.define do
end end
trait :teardown_environment do trait :teardown_environment do
options do environment 'staging'
{ environment: { action: 'stop' } } options environment: { name: 'staging',
end action: 'stop' }
end end
trait :allowed_to_fail do trait :allowed_to_fail do
allow_failure true allow_failure true
end end
trait :playable do
skipped
manual
end
after(:build) do |build, evaluator| after(:build) do |build, evaluator|
build.project = build.pipeline.project build.project = build.pipeline.project
end end

View File

@ -0,0 +1,27 @@
require 'spec_helper'
describe Gitlab::Allowable do
subject do
Class.new.include(described_class).new
end
describe '#can?' do
let(:user) { create(:user) }
context 'when user is allowed to do something' do
let(:project) { create(:empty_project, :public) }
it 'reports correct ability to perform action' do
expect(subject.can?(user, :read_project, project)).to be true
end
end
context 'when user is not allowed to do something' do
let(:project) { create(:empty_project, :private) }
it 'reports correct ability to perform action' do
expect(subject.can?(user, :read_project, project)).to be false
end
end
end
end

View File

@ -0,0 +1,86 @@
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Cancelable do
let(:status) { double('core status') }
let(:user) { double('user') }
subject do
described_class.new(status)
end
describe '#text' do
it 'does not override status text' do
expect(status).to receive(:text)
subject.text
end
end
describe '#icon' do
it 'does not override status icon' do
expect(status).to receive(:icon)
subject.icon
end
end
describe '#label' do
it 'does not override status label' do
expect(status).to receive(:label)
subject.label
end
end
describe 'action details' do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
describe '#has_action?' do
context 'when user is allowed to update build' do
before { build.project.team << [user, :developer] }
it { is_expected.to have_action }
end
context 'when user is not allowed to update build' do
it { is_expected.not_to have_action }
end
end
describe '#action_path' do
it { expect(subject.action_path).to include "#{build.id}/cancel" }
end
describe '#action_icon' do
it { expect(subject.action_icon).to eq 'ban' }
end
describe '#action_title' do
it { expect(subject.action_title).to eq 'Cancel' }
end
end
describe '.matches?' do
subject { described_class.matches?(build, user) }
context 'when build is cancelable' do
let(:build) do
create(:ci_build, :running)
end
it 'is a correct match' do
expect(subject).to be true
end
end
context 'when build is not cancelable' do
let(:build) { create(:ci_build, :success) }
it 'does not match' do
expect(subject).to be false
end
end
end
end

View File

@ -0,0 +1,37 @@
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Common do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:project) { build.project }
subject do
Gitlab::Ci::Status::Core
.new(build, user)
.extend(described_class)
end
describe '#has_action?' do
it { is_expected.not_to have_action }
end
describe '#has_details?' do
context 'when user has access to read build' do
before { project.team << [user, :developer] }
it { is_expected.to have_details }
end
context 'when user does not have access to read build' do
before { project.update(public_builds: false) }
it { is_expected.not_to have_details }
end
end
describe '#details_path' do
it 'links to the build details page' do
expect(subject.details_path).to include "builds/#{build.id}"
end
end
end

View File

@ -0,0 +1,141 @@
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Factory do
let(:user) { create(:user) }
let(:project) { build.project }
subject { described_class.new(build, user) }
let(:status) { subject.fabricate! }
before { project.team << [user, :developer] }
context 'when build is successful' do
let(:build) { create(:ci_build, :success) }
it 'fabricates a retryable build status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'passed'
expect(status.icon).to eq 'icon_status_success'
expect(status.label).to eq 'passed'
expect(status).to have_details
expect(status).to have_action
end
end
context 'when build is failed' do
let(:build) { create(:ci_build, :failed) }
it 'fabricates a retryable build status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'failed'
expect(status.icon).to eq 'icon_status_failed'
expect(status.label).to eq 'failed'
expect(status).to have_details
expect(status).to have_action
end
end
context 'when build is a canceled' do
let(:build) { create(:ci_build, :canceled) }
it 'fabricates a retryable build status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'canceled'
expect(status.icon).to eq 'icon_status_canceled'
expect(status.label).to eq 'canceled'
expect(status).to have_details
expect(status).to have_action
end
end
context 'when build is running' do
let(:build) { create(:ci_build, :running) }
it 'fabricates a canceable build status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'running'
expect(status.icon).to eq 'icon_status_running'
expect(status.label).to eq 'running'
expect(status).to have_details
expect(status).to have_action
end
end
context 'when build is pending' do
let(:build) { create(:ci_build, :pending) }
it 'fabricates a cancelable build status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'pending'
expect(status.icon).to eq 'icon_status_pending'
expect(status.label).to eq 'pending'
expect(status).to have_details
expect(status).to have_action
end
end
context 'when build is skipped' do
let(:build) { create(:ci_build, :skipped) }
it 'fabricates a core skipped status' do
expect(status).to be_a Gitlab::Ci::Status::Skipped
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'skipped'
expect(status.icon).to eq 'icon_status_skipped'
expect(status.label).to eq 'skipped'
expect(status).to have_details
expect(status).not_to have_action
end
end
context 'when build is a manual action' do
context 'when build is a play action' do
let(:build) { create(:ci_build, :playable) }
it 'fabricates a core skipped status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Play
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual play action'
expect(status).to have_details
expect(status).to have_action
end
end
context 'when build is an environment stop action' do
let(:build) { create(:ci_build, :playable, :teardown_environment) }
it 'fabricates a core skipped status' do
expect(status).to be_a Gitlab::Ci::Status::Build::Stop
end
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.icon).to eq 'icon_status_manual'
expect(status.label).to eq 'manual stop action'
expect(status).to have_details
expect(status).to have_action
end
end
end
end

View File

@ -0,0 +1,82 @@
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do
let(:status) { double('core') }
let(:user) { double('user') }
subject { described_class.new(status) }
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do
it { expect(subject.label).to eq 'manual play action' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe 'action details' do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
describe '#has_action?' do
context 'when user is allowed to update build' do
before { build.project.team << [user, :developer] }
it { is_expected.to have_action }
end
context 'when user is not allowed to update build' do
it { is_expected.not_to have_action }
end
end
describe '#action_path' do
it { expect(subject.action_path).to include "#{build.id}/play" }
end
describe '#action_icon' do
it { expect(subject.action_icon).to eq 'play' }
end
describe '#action_title' do
it { expect(subject.action_title).to eq 'Play' }
end
end
describe '.matches?' do
subject { described_class.matches?(build, user) }
context 'when build is playable' do
context 'when build stops an environment' do
let(:build) do
create(:ci_build, :playable, :teardown_environment)
end
it 'does not match' do
expect(subject).to be false
end
end
context 'when build does not stop an environment' do
let(:build) { create(:ci_build, :playable) }
it 'is a correct match' do
expect(subject).to be true
end
end
end
context 'when build is not playable' do
let(:build) { create(:ci_build) }
it 'does not match' do
expect(subject).to be false
end
end
end
end

View File

@ -0,0 +1,86 @@
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Retryable do
let(:status) { double('core status') }
let(:user) { double('user') }
subject do
described_class.new(status)
end
describe '#text' do
it 'does not override status text' do
expect(status).to receive(:text)
subject.text
end
end
describe '#icon' do
it 'does not override status icon' do
expect(status).to receive(:icon)
subject.icon
end
end
describe '#label' do
it 'does not override status label' do
expect(status).to receive(:label)
subject.label
end
end
describe 'action details' do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
describe '#has_action?' do
context 'when user is allowed to update build' do
before { build.project.team << [user, :developer] }
it { is_expected.to have_action }
end
context 'when user is not allowed to update build' do
it { is_expected.not_to have_action }
end
end
describe '#action_path' do
it { expect(subject.action_path).to include "#{build.id}/retry" }
end
describe '#action_icon' do
it { expect(subject.action_icon).to eq 'refresh' }
end
describe '#action_title' do
it { expect(subject.action_title).to eq 'Retry' }
end
end
describe '.matches?' do
subject { described_class.matches?(build, user) }
context 'when build is retryable' do
let(:build) do
create(:ci_build, :success)
end
it 'is a correct match' do
expect(subject).to be true
end
end
context 'when build is not retryable' do
let(:build) { create(:ci_build, :running) }
it 'does not match' do
expect(subject).to be false
end
end
end
end

View File

@ -0,0 +1,84 @@
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Stop do
let(:status) { double('core status') }
let(:user) { double('user') }
subject do
described_class.new(status)
end
describe '#text' do
it { expect(subject.text).to eq 'manual' }
end
describe '#label' do
it { expect(subject.label).to eq 'manual stop action' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_manual' }
end
describe 'action details' do
let(:user) { create(:user) }
let(:build) { create(:ci_build) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
describe '#has_action?' do
context 'when user is allowed to update build' do
before { build.project.team << [user, :developer] }
it { is_expected.to have_action }
end
context 'when user is not allowed to update build' do
it { is_expected.not_to have_action }
end
end
describe '#action_path' do
it { expect(subject.action_path).to include "#{build.id}/play" }
end
describe '#action_icon' do
it { expect(subject.action_icon).to eq 'stop' }
end
describe '#action_title' do
it { expect(subject.action_title).to eq 'Stop' }
end
end
describe '.matches?' do
subject { described_class.matches?(build, user) }
context 'when build is playable' do
context 'when build stops an environment' do
let(:build) do
create(:ci_build, :playable, :teardown_environment)
end
it 'is a correct match' do
expect(subject).to be true
end
end
context 'when build does not stop an environment' do
let(:build) { create(:ci_build, :playable) }
it 'does not match' do
expect(subject).to be false
end
end
end
context 'when build is not playable' do
let(:build) { create(:ci_build) }
it 'does not match' do
expect(subject).to be false
end
end
end
end

View File

@ -1,7 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Canceled do describe Gitlab::Ci::Status::Canceled do
subject { described_class.new(double('subject')) } subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'canceled' } it { expect(subject.label).to eq 'canceled' }
@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Canceled do
describe '#icon' do describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_canceled' } it { expect(subject.icon).to eq 'icon_status_canceled' }
end end
describe '#title' do
it { expect(subject.title).to eq 'Double: canceled' }
end
end end

View File

@ -1,7 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Created do describe Gitlab::Ci::Status::Created do
subject { described_class.new(double('subject')) } subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'created' } it { expect(subject.label).to eq 'created' }
@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Created do
describe '#icon' do describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_created' } it { expect(subject.icon).to eq 'icon_status_created' }
end end
describe '#title' do
it { expect(subject.title).to eq 'Double: created' }
end
end end

View File

@ -2,11 +2,11 @@ require 'spec_helper'
describe Gitlab::Ci::Status::Extended do describe Gitlab::Ci::Status::Extended do
subject do subject do
Class.new.extend(described_class) Class.new.include(described_class)
end end
it 'requires subclass to implement matcher' do it 'requires subclass to implement matcher' do
expect { subject.matches?(double) } expect { subject.matches?(double, double) }
.to raise_error(NotImplementedError) .to raise_error(NotImplementedError)
end end
end end

View File

@ -2,15 +2,17 @@ require 'spec_helper'
describe Gitlab::Ci::Status::Factory do describe Gitlab::Ci::Status::Factory do
subject do subject do
described_class.new(object) described_class.new(resource, user)
end end
let(:user) { create(:user) }
let(:status) { subject.fabricate! } let(:status) { subject.fabricate! }
context 'when object has a core status' do context 'when object has a core status' do
HasStatus::AVAILABLE_STATUSES.each do |core_status| HasStatus::AVAILABLE_STATUSES.each do |core_status|
context "when core status is #{core_status}" do context "when core status is #{core_status}" do
let(:object) { double(status: core_status) } let(:resource) { double(status: core_status) }
it "fabricates a core status #{core_status}" do it "fabricates a core status #{core_status}" do
expect(status).to be_a( expect(status).to be_a(

View File

@ -1,7 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Failed do describe Gitlab::Ci::Status::Failed do
subject { described_class.new(double('subject')) } subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'failed' } it { expect(subject.label).to eq 'failed' }
@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Failed do
describe '#icon' do describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_failed' } it { expect(subject.icon).to eq 'icon_status_failed' }
end end
describe '#title' do
it { expect(subject.title).to eq 'Double: failed' }
end
end end

View File

@ -1,7 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Pending do describe Gitlab::Ci::Status::Pending do
subject { described_class.new(double('subject')) } subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'pending' } it { expect(subject.label).to eq 'pending' }
@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Pending do
describe '#icon' do describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_pending' } it { expect(subject.icon).to eq 'icon_status_pending' }
end end
describe '#title' do
it { expect(subject.title).to eq 'Double: pending' }
end
end end

View File

@ -1,23 +1,36 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Common do describe Gitlab::Ci::Status::Pipeline::Common do
let(:pipeline) { create(:ci_pipeline) } let(:user) { create(:user) }
let(:project) { create(:empty_project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
subject do subject do
Class.new(Gitlab::Ci::Status::Core) Gitlab::Ci::Status::Core
.new(pipeline).extend(described_class) .new(pipeline, user)
.extend(described_class)
end end
it 'does not have action' do describe '#has_action?' do
expect(subject).not_to have_action it { is_expected.not_to have_action }
end end
it 'has details' do describe '#has_details?' do
expect(subject).to have_details context 'when user has access to read pipeline' do
before { project.team << [user, :developer] }
it { is_expected.to have_details }
end
context 'when user does not have access to read pipeline' do
it { is_expected.not_to have_details }
end
end end
it 'links to the pipeline details page' do describe '#details_path' do
expect(subject.details_path) it 'links to the pipeline details page' do
.to include "pipelines/#{pipeline.id}" expect(subject.details_path)
.to include "pipelines/#{pipeline.id}"
end
end end
end end

View File

@ -1,14 +1,21 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Factory do describe Gitlab::Ci::Status::Pipeline::Factory do
let(:user) { create(:user) }
let(:project) { pipeline.project }
subject do subject do
described_class.new(pipeline) described_class.new(pipeline, user)
end end
let(:status) do let(:status) do
subject.fabricate! subject.fabricate!
end end
before do
project.team << [user, :developer]
end
context 'when pipeline has a core status' do context 'when pipeline has a core status' do
HasStatus::AVAILABLE_STATUSES.each do |core_status| HasStatus::AVAILABLE_STATUSES.each do |core_status|
context "when core status is #{core_status}" do context "when core status is #{core_status}" do

View File

@ -29,13 +29,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
end end
it 'is a correct match' do it 'is a correct match' do
expect(described_class.matches?(pipeline)).to eq true expect(described_class.matches?(pipeline, double)).to eq true
end end
end end
context 'when pipeline does not have warnings' do context 'when pipeline does not have warnings' do
it 'does not match' do it 'does not match' do
expect(described_class.matches?(pipeline)).to eq false expect(described_class.matches?(pipeline, double)).to eq false
end end
end end
end end
@ -51,13 +51,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
end end
it 'does not match' do it 'does not match' do
expect(described_class.matches?(pipeline)).to eq false expect(described_class.matches?(pipeline, double)).to eq false
end end
end end
context 'when pipeline does not have warnings' do context 'when pipeline does not have warnings' do
it 'does not match' do it 'does not match' do
expect(described_class.matches?(pipeline)).to eq false expect(described_class.matches?(pipeline, double)).to eq false
end end
end end
end end

View File

@ -1,7 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Running do describe Gitlab::Ci::Status::Running do
subject { described_class.new(double('subject')) } subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'running' } it { expect(subject.label).to eq 'running' }
@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Running do
describe '#icon' do describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_running' } it { expect(subject.icon).to eq 'icon_status_running' }
end end
describe '#title' do
it { expect(subject.title).to eq 'Double: running' }
end
end end

View File

@ -1,7 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Skipped do describe Gitlab::Ci::Status::Skipped do
subject { described_class.new(double('subject')) } subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'skipped' } it { expect(subject.label).to eq 'skipped' }
@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Skipped do
describe '#icon' do describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_skipped' } it { expect(subject.icon).to eq 'icon_status_skipped' }
end end
describe '#title' do
it { expect(subject.title).to eq 'Double: skipped' }
end
end end

View File

@ -1,26 +1,43 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Common do describe Gitlab::Ci::Status::Stage::Common do
let(:pipeline) { create(:ci_empty_pipeline) } let(:user) { create(:user) }
let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:stage) do
build(:ci_stage, pipeline: pipeline, name: 'test')
end
subject do subject do
Class.new(Gitlab::Ci::Status::Core) Class.new(Gitlab::Ci::Status::Core)
.new(stage).extend(described_class) .new(stage, user).extend(described_class)
end end
it 'does not have action' do it 'does not have action' do
expect(subject).not_to have_action expect(subject).not_to have_action
end end
it 'has details' do
expect(subject).to have_details
end
it 'links to the pipeline details page' do it 'links to the pipeline details page' do
expect(subject.details_path) expect(subject.details_path)
.to include "pipelines/#{pipeline.id}" .to include "pipelines/#{pipeline.id}"
expect(subject.details_path) expect(subject.details_path)
.to include "##{stage.name}" .to include "##{stage.name}"
end end
context 'when user has permission to read pipeline' do
before do
project.team << [user, :master]
end
it 'has details' do
expect(subject).to have_details
end
end
context 'when user does not have permission to read pipeline' do
it 'does not have details' do
expect(subject).not_to have_details
end
end
end end

View File

@ -1,17 +1,26 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Factory do describe Gitlab::Ci::Status::Stage::Factory do
let(:pipeline) { create(:ci_empty_pipeline) } let(:user) { create(:user) }
let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:stage) do
build(:ci_stage, pipeline: pipeline, name: 'test')
end
subject do subject do
described_class.new(stage) described_class.new(stage, user)
end end
let(:status) do let(:status) do
subject.fabricate! subject.fabricate!
end end
before do
project.team << [user, :developer]
end
context 'when stage has a core status' do context 'when stage has a core status' do
HasStatus::AVAILABLE_STATUSES.each do |core_status| HasStatus::AVAILABLE_STATUSES.each do |core_status|
context "when core status is #{core_status}" do context "when core status is #{core_status}" do

View File

@ -1,7 +1,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Status::Success do describe Gitlab::Ci::Status::Success do
subject { described_class.new(double('subject')) } subject do
described_class.new(double('subject'), double('user'))
end
describe '#text' do describe '#text' do
it { expect(subject.label).to eq 'passed' } it { expect(subject.label).to eq 'passed' }
@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Success do
describe '#icon' do describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_success' } it { expect(subject.icon).to eq 'icon_status_success' }
end end
describe '#title' do
it { expect(subject.title).to eq 'Double: passed' }
end
end end

View File

@ -0,0 +1,23 @@
require 'spec_helper'
describe Gitlab::Routing do
context 'when module is included' do
subject do
Class.new.include(described_class).new
end
it 'makes it possible to access url helpers' do
expect(subject).to respond_to(:namespace_project_path)
end
end
context 'when module is not included' do
subject do
Class.new.include(described_class.url_helpers).new
end
it 'exposes url helpers module through a method' do
expect(subject).to respond_to(:namespace_project_path)
end
end
end

View File

@ -899,21 +899,87 @@ describe Ci::Build, models: true do
end end
end end
describe '#retryable?' do describe '#cancelable?' do
context 'when build is running' do subject { build }
before do
build.run! context 'when build is cancelable' do
context 'when build is pending' do
it { is_expected.to be_cancelable }
end end
it { expect(build).not_to be_retryable } context 'when build is running' do
before do
build.run!
end
it { is_expected.to be_cancelable }
end
end end
context 'when build is finished' do context 'when build is not cancelable' do
before do context 'when build is successful' do
build.success! before do
build.success!
end
it { is_expected.not_to be_cancelable }
end end
it { expect(build).to be_retryable } context 'when build is failed' do
before do
build.drop!
end
it { is_expected.not_to be_cancelable }
end
end
end
describe '#retryable?' do
subject { build }
context 'when build is retryable' do
context 'when build is successful' do
before do
build.success!
end
it { is_expected.to be_retryable }
end
context 'when build is failed' do
before do
build.drop!
end
it { is_expected.to be_retryable }
end
context 'when build is canceled' do
before do
build.cancel!
end
it { is_expected.to be_retryable }
end
end
context 'when build is not retryable' do
context 'when build is running' do
before do
build.run!
end
it { is_expected.not_to be_retryable }
end
context 'when build is skipped' do
before do
build.skip!
end
it { is_expected.not_to be_retryable }
end
end end
end end
@ -1180,4 +1246,13 @@ describe Ci::Build, models: true do
it { is_expected.to eq('review/master') } it { is_expected.to eq('review/master') }
end end
end end
describe '#detailed_status' do
let(:user) { create(:user) }
it 'returns a detailed status' do
expect(build.detailed_status(user))
.to be_a Gitlab::Ci::Status::Build::Cancelable
end
end
end end

View File

@ -442,11 +442,15 @@ describe Ci::Pipeline, models: true do
end end
describe '#detailed_status' do describe '#detailed_status' do
let(:user) { create(:user) }
subject { pipeline.detailed_status(user) }
context 'when pipeline is created' do context 'when pipeline is created' do
let(:pipeline) { create(:ci_pipeline, status: :created) } let(:pipeline) { create(:ci_pipeline, status: :created) }
it 'returns detailed status for created pipeline' do it 'returns detailed status for created pipeline' do
expect(pipeline.detailed_status.text).to eq 'created' expect(subject.text).to eq 'created'
end end
end end
@ -454,7 +458,7 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_pipeline, status: :pending) } let(:pipeline) { create(:ci_pipeline, status: :pending) }
it 'returns detailed status for pending pipeline' do it 'returns detailed status for pending pipeline' do
expect(pipeline.detailed_status.text).to eq 'pending' expect(subject.text).to eq 'pending'
end end
end end
@ -462,7 +466,7 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_pipeline, status: :running) } let(:pipeline) { create(:ci_pipeline, status: :running) }
it 'returns detailed status for running pipeline' do it 'returns detailed status for running pipeline' do
expect(pipeline.detailed_status.text).to eq 'running' expect(subject.text).to eq 'running'
end end
end end
@ -470,7 +474,7 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_pipeline, status: :success) } let(:pipeline) { create(:ci_pipeline, status: :success) }
it 'returns detailed status for successful pipeline' do it 'returns detailed status for successful pipeline' do
expect(pipeline.detailed_status.text).to eq 'passed' expect(subject.text).to eq 'passed'
end end
end end
@ -478,7 +482,7 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_pipeline, status: :failed) } let(:pipeline) { create(:ci_pipeline, status: :failed) }
it 'returns detailed status for failed pipeline' do it 'returns detailed status for failed pipeline' do
expect(pipeline.detailed_status.text).to eq 'failed' expect(subject.text).to eq 'failed'
end end
end end
@ -486,7 +490,7 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_pipeline, status: :canceled) } let(:pipeline) { create(:ci_pipeline, status: :canceled) }
it 'returns detailed status for canceled pipeline' do it 'returns detailed status for canceled pipeline' do
expect(pipeline.detailed_status.text).to eq 'canceled' expect(subject.text).to eq 'canceled'
end end
end end
@ -494,7 +498,7 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_pipeline, status: :skipped) } let(:pipeline) { create(:ci_pipeline, status: :skipped) }
it 'returns detailed status for skipped pipeline' do it 'returns detailed status for skipped pipeline' do
expect(pipeline.detailed_status.text).to eq 'skipped' expect(subject.text).to eq 'skipped'
end end
end end
@ -506,7 +510,7 @@ describe Ci::Pipeline, models: true do
end end
it 'retruns detailed status for successful pipeline with warnings' do it 'retruns detailed status for successful pipeline with warnings' do
expect(pipeline.detailed_status.label).to eq 'passed with warnings' expect(subject.label).to eq 'passed with warnings'
end end
end end
end end

View File

@ -68,7 +68,9 @@ describe Ci::Stage, models: true do
end end
describe '#detailed_status' do describe '#detailed_status' do
subject { stage.detailed_status } let(:user) { create(:user) }
subject { stage.detailed_status(user) }
context 'when build is created' do context 'when build is created' do
let!(:stage_build) { create_job(:ci_build, status: :created) } let!(:stage_build) { create_job(:ci_build, status: :created) }

View File

@ -234,4 +234,13 @@ describe CommitStatus, models: true do
end end
end end
end end
describe '#detailed_status' do
let(:user) { create(:user) }
it 'returns a detailed status' do
expect(commit_status.detailed_status(user))
.to be_a Gitlab::Ci::Status::Success
end
end
end end

View File

@ -1,8 +1,11 @@
require 'spec_helper' require 'spec_helper'
describe GenericCommitStatus, models: true do describe GenericCommitStatus, models: true do
let(:pipeline) { FactoryGirl.create :ci_pipeline } let(:pipeline) { create(:ci_pipeline) }
let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
let(:generic_commit_status) do
create(:generic_commit_status, pipeline: pipeline)
end
describe '#context' do describe '#context' do
subject { generic_commit_status.context } subject { generic_commit_status.context }
@ -17,6 +20,15 @@ describe GenericCommitStatus, models: true do
it { is_expected.to eq([:external]) } it { is_expected.to eq([:external]) }
end end
describe '#detailed_status' do
let(:user) { create(:user) }
it 'returns detailed status object' do
expect(generic_commit_status.detailed_status(user))
.to be_a Gitlab::Ci::Status::Success
end
end
describe 'set_default_values' do describe 'set_default_values' do
before do before do
generic_commit_status.context = nil generic_commit_status.context = nil