Commit 3e90aa11 authored by Rémy Coutable's avatar Rémy Coutable

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
parents 3a906126 6757aaa1
Pipeline #5394321 passed with stages
in 105 minutes and 12 seconds
......@@ -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,12 @@ module Ci
end
end
def detailed_status(current_user)
Gitlab::Ci::Status::Build::Factory
.new(self, current_user)
.fabricate!
end
def manual?
self.when == 'manual'
end
......@@ -123,8 +129,13 @@ module Ci
end
end
def cancelable?
active?
end
def retryable?
project.builds_enabled? && commands.present? && complete?
project.builds_enabled? && commands.present? &&
(success? || failed? || canceled?)
end
def retried?
......@@ -148,7 +159,7 @@ module Ci
end
def environment_action
self.options.fetch(:environment, {}).fetch(:action, 'start')
self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options
end
def outdated_deployment?
......
......@@ -336,8 +336,10 @@ module Ci
.select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
end
def detailed_status
Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate!
def detailed_status(current_user)
Gitlab::Ci::Status::Pipeline::Factory
.new(self, current_user)
.fabricate!
end
private
......
......@@ -22,8 +22,10 @@ module Ci
@status ||= statuses.latest.status
end
def detailed_status
Gitlab::Ci::Status::Stage::Factory.new(self).fabricate!
def detailed_status(current_user)
Gitlab::Ci::Status::Stage::Factory
.new(self, current_user)
.fabricate!
end
def statuses
......
......@@ -131,4 +131,10 @@ class CommitStatus < ActiveRecord::Base
def has_trace?
false
end
def detailed_status(current_user)
Gitlab::Ci::Status::Factory
.new(self, current_user)
.fabricate!
end
end
......@@ -91,7 +91,7 @@
%strong ##{build.id}
%td.status
= ci_status_with_icon(build.status)
= render 'ci/status/badge', status: build.detailed_status(current_user)
%td.status
- if project
......
- 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
.content-block.build-header
.header-content
= ci_status_with_icon(@build.status)
= render 'ci/status/badge', status: @build.detailed_status(current_user)
Build
%strong ##{@build.id}
in pipeline
......
......@@ -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/badge", status: build.detailed_status(current_user)
%td.branch-commit
- if can?(current_user, :read_build, build)
......
- 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/badge', status: pipeline.detailed_status(current_user)
%td
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
......
......@@ -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/badge', status: generic_commit_status.detailed_status(current_user)
%td.generic_commit_status-link
- if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
......
.page-content-header
.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}
triggered #{time_ago_with_tooltip(@commit.authored_date)} by
= author_avatar(@commit, size: 24)
......
......@@ -19,4 +19,4 @@
%li.build
.curve
.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
<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>
module Gitlab
module Allowable
def can?(user, action, subject)
Ability.allowed?(user, action, subject)
end
end
end
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
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
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
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
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
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
......@@ -4,10 +4,14 @@ module Gitlab
# Base abstract class fore core status
#
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
@user = user
end
def icon
......@@ -18,10 +22,6 @@ module Gitlab
raise NotImplementedError
end
def title
"#{@subject.class.name.demodulize}: #{label}"
end
# Deprecation warning: this method is here because we need to maintain
# backwards compatibility with legacy statuses. We often do something
# like "ci-status ci-status-#{status}" to set CSS class.
......@@ -34,7 +34,7 @@ module Gitlab
end
def has_details?
raise NotImplementedError
false
end
def details_path
......@@ -42,16 +42,27 @@ module Gitlab
end
def has_action?
raise NotImplementedError
false
end
def action_icon
raise NotImplementedError
end
def action_class
end
def action_path
raise NotImplementedError
end
def action_method
raise NotImplementedError
end
def action_title
raise NotImplementedError
end
end
end
end
......
......@@ -2,8 +2,12 @@ module Gitlab
module Ci
module Status
module Extended
def matches?(_subject)
raise NotImplementedError
extend ActiveSupport::Concern
class_methods do
def matches?(_subject, _user)
raise NotImplementedError
end
end
end
end
......
......@@ -2,10 +2,9 @@ module Gitlab
module Ci
module Status
class Factory
attr_reader :subject
def initialize(subject)
def initialize(subject, user)
@subject = subject
@user = user
end
def fabricate!
......@@ -16,27 +15,32 @@ module Gitlab
end
end
def self.extended_statuses
[]
end
def self.common_helpers
Module.new
end
private
def subject_status
@subject_status ||= subject.status
def simple_status
@simple_status ||= @subject.status || :created
end
def core_status
Gitlab::Ci::Status
.const_get(subject_status.capitalize)
.new(subject)
.const_get(simple_status.capitalize)
.new(@subject, @user)
.extend(self.class.common_helpers)
end
def extended_status
@extended ||= extended_statuses.find do |status|
status.matches?(subject)
@extended ||= self.class.extended_statuses.find do |status|
status.matches?(@subject, @user)
end
end
def extended_statuses
[]
end
end
end
end
......
......@@ -4,13 +4,13 @@ module Gitlab
module Pipeline
module Common
def has_details?
true
can?(user, :read_pipeline, subject)
end
def details_path
namespace_project_pipeline_path(@subject.project.namespace,
@subject.project,
@subject)
namespace_project_pipeline_path(subject.project.namespace,
subject.project,
subject)
end
def has_action?
......
......@@ -3,14 +3,12 @@ module Gitlab
module Status
module Pipeline
class Factory < Status::Factory
private
def extended_statuses
def self.extended_statuses
[Pipeline::SuccessWithWarnings]
end
def core_status
super.extend(Status::Pipeline::Common)
def self.common_helpers
Status::Pipeline::Common
end
end
end
......
......@@ -3,7 +3,7 @@ module Gitlab
module Status
module Pipeline
class SuccessWithWarnings < SimpleDelegator
extend Status::Extended
include Status::Extended
def text
'passed'
......@@ -21,7 +21,7 @@ module Gitlab
'success_with_warnings'
end
def self.matches?(pipeline)
def self.matches?(pipeline, user)
pipeline.success? && pipeline.has_warnings?
end
end
......
......@@ -4,14 +4,14 @@ module Gitlab
module Stage
module Common
def has_details?
true
can?(user, :read_pipeline, subject.pipeline)
end
def details_path
namespace_project_pipeline_path(@subject.project.namespace,
@subject.project,
@subject.pipeline,
anchor: @subject.name)
namespace_project_pipeline_path(subject.project.namespace,
subject.project,
subject.pipeline,
anchor: subject.name)
end
def has_action?
......
......@@ -3,10 +3,8 @@ module Gitlab
module Status
module Stage
class Factory < Status::Factory
private
def core_status
super.extend(Status::Stage::Common)
def self.common_helpers
Status::Stage::Common
end
end
end
......
module Gitlab
module Routing
extend ActiveSupport::Concern
included do
include Gitlab::Routing.url_helpers
end
# Returns the URL helpers Module.
#
# This method caches the output as Rails' "url_helpers" method creates an
......
......@@ -12,12 +12,14 @@ FactoryGirl.define do
started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a'
options do
{
image: "ruby:2.1",
services: ["postgres"]
}
end
yaml_variables do
[
{ key: :DB_NAME, value: 'postgres', public: true }
......@@ -60,15 +62,20 @@ FactoryGirl.define do
end
trait :teardown_environment do
options do
{ environment: { action: 'stop' } }
end
environment 'staging'
options environment: { name: 'staging',
action: 'stop' }
end
trait :allowed_to_fail do
allow_failure true
end
trait :playable do
skipped
manual
end
after(:build) do |build, evaluator|
build.project = build.pipeline.project
end
......
require 'spec_helper'
describe Gitlab::Allowable do
subject do
Class.new.include(described_class).new
end
describe '#can?' do
let(:user) { create(:user) }