Commit 532c0319 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'refine-ci-statuses' into 'master'

Refine CI Statuses

## What does this MR do?

This MR introduces classes for each relevant CI status.

## What are the relevant issue numbers?

Closes #24273

See merge request !7889
parents da5c3725 7b99b186
...@@ -5,8 +5,9 @@ module CiStatusHelper ...@@ -5,8 +5,9 @@ module CiStatusHelper
end end
def ci_status_with_icon(status, target = nil) def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + ci_label_for_status(status) content = ci_icon_for_status(status) + ci_text_for_status(status)
klass = "ci-status ci-#{status}" klass = "ci-status ci-#{status}"
if target if target
link_to content, target, class: klass link_to content, target, class: klass
else else
...@@ -14,7 +15,19 @@ module CiStatusHelper ...@@ -14,7 +15,19 @@ module CiStatusHelper
end end
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)
return status.label
end
case status case status
when 'success' when 'success'
'passed' 'passed'
...@@ -31,6 +44,10 @@ module CiStatusHelper ...@@ -31,6 +44,10 @@ module CiStatusHelper
end end
def ci_icon_for_status(status) def ci_icon_for_status(status)
if detailed_status?(status)
return custom_icon(status.icon)
end
icon_name = icon_name =
case status case status
when 'success' when 'success'
...@@ -94,4 +111,10 @@ module CiStatusHelper ...@@ -94,4 +111,10 @@ module CiStatusHelper
class: klass, title: title, data: data class: klass, title: title, data: data
end end
end end
def detailed_status?(status)
status.respond_to?(:text) &&
status.respond_to?(:label) &&
status.respond_to?(:icon)
end
end end
...@@ -320,6 +320,10 @@ module Ci ...@@ -320,6 +320,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
Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate!
end
private private
def pipeline_data def pipeline_data
......
- 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-#{status}" do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do
= ci_icon_for_status(status) = ci_icon_for_status(detailed_status)
= ci_label_for_status(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
......
...@@ -8,14 +8,13 @@ ...@@ -8,14 +8,13 @@
= link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline' = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline'
= ci_label_for_status(status) = ci_label_for_status(status)
for for
- commit = @merge_request.diff_head_commit
= succeed "." do = succeed "." do
= link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
%span.ci-coverage %span.ci-coverage
- elsif @merge_request.has_ci? - elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- # Remove in later versions when services like Jenkins will set CI status via Commit status API - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading .mr-widget-heading
- %w[success skipped canceled failed running pending].each do |status| - %w[success skipped canceled failed running pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"} .ci_widget{class: "ci-#{status}", style: "display:none"}
......
.page-content-header .page-content-header
.header-main-content .header-main-content
= ci_status_with_icon(@pipeline.status) = ci_status_with_icon(@pipeline.detailed_status)
%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)
......
module Gitlab
module Ci
module Status
class Canceled < Status::Core
def text
'canceled'
end
def label
'canceled'
end
def icon
'icon_status_canceled'
end
end
end
end
end
module Gitlab
module Ci
module Status
# Base abstract class fore core status
#
class Core
include Gitlab::Routing.url_helpers
def initialize(subject)
@subject = subject
end
def icon
raise NotImplementedError
end
def label
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.
#
# `to_s` method should be renamed to `group` at some point, after
# phasing legacy satuses out.
#
def to_s
self.class.name.demodulize.downcase.underscore
end
def has_details?
raise NotImplementedError
end
def details_path
raise NotImplementedError
end
def has_action?
raise NotImplementedError
end
def action_icon
raise NotImplementedError
end
def action_path
raise NotImplementedError
end
end
end
end
end
module Gitlab
module Ci
module Status
class Created < Status::Core
def text
'created'
end
def label
'created'
end
def icon
'icon_status_created'
end
end
end
end
end
module Gitlab
module Ci
module Status
module Extended
def matches?(_subject)
raise NotImplementedError
end
end
end
end
end
module Gitlab
module Ci
module Status
class Failed < Status::Core
def text
'failed'
end
def label
'failed'
end
def icon
'icon_status_failed'
end
end
end
end
end
module Gitlab
module Ci
module Status
class Pending < Status::Core
def text
'pending'
end
def label
'pending'
end
def icon
'icon_status_pending'
end
end
end
end
end
module Gitlab
module Ci
module Status
module Pipeline
module Common
def has_details?
true
end
def details_path
namespace_project_pipeline_path(@subject.project.namespace,
@subject.project,
@subject)
end
def has_action?
false
end
end
end
end
end
end
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
private
def core_status
Gitlab::Ci::Status
.const_get(@status.capitalize)
.new(@pipeline)
.extend(Status::Pipeline::Common)
end
def extended_status
@extended ||= EXTENDED_STATUSES.find do |status|
status.matches?(@pipeline)
end
end
end
end
end
end
end
module Gitlab
module Ci
module Status
module Pipeline
class SuccessWithWarnings < SimpleDelegator
extend Status::Extended
def text
'passed'
end
def label
'passed with warnings'
end
def icon
'icon_status_warning'
end
def to_s
'success_with_warnings'
end
def self.matches?(pipeline)
pipeline.success? && pipeline.has_warnings?
end
end
end
end
end
end
module Gitlab
module Ci
module Status
class Running < Status::Core
def text
'running'
end
def label
'running'
end
def icon
'icon_status_running'
end
end
end
end
end
module Gitlab
module Ci
module Status
class Skipped < Status::Core
def text
'skipped'
end
def label
'skipped'
end
def icon
'icon_status_skipped'
end
end
end
end
end
module Gitlab
module Ci
module Status
class Success < Status::Core
def text
'passed'
end
def label
'passed'
end
def icon
'icon_status_success'
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Canceled do
subject { described_class.new(double('subject')) }
describe '#text' do
it { expect(subject.label).to eq 'canceled' }
end
describe '#label' do
it { expect(subject.label).to eq 'canceled' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_canceled' }
end
describe '#title' do
it { expect(subject.title).to eq 'Double: canceled' }
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Created do
subject { described_class.new(double('subject')) }
describe '#text' do
it { expect(subject.label).to eq 'created' }
end
describe '#label' do
it { expect(subject.label).to eq 'created' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_created' }
end
describe '#title' do
it { expect(subject.title).to eq 'Double: created' }
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Extended do
subject do
Class.new.extend(described_class)
end
it 'requires subclass to implement matcher' do
expect { subject.matches?(double) }
.to raise_error(NotImplementedError)
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Failed do
subject { described_class.new(double('subject')) }
describe '#text' do
it { expect(subject.label).to eq 'failed' }
end
describe '#label' do
it { expect(subject.label).to eq 'failed' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_failed' }
end
describe '#title' do
it { expect(subject.title).to eq 'Double: failed' }
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Pending do
subject { described_class.new(double('subject')) }
describe '#text' do
it { expect(subject.label).to eq 'pending' }
end
describe '#label' do
it { expect(subject.label).to eq 'pending' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_pending' }
end
describe '#title' do
it { expect(subject.title).to eq 'Double: pending' }
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Common do
let(:pipeline) { create(:ci_pipeline) }
subject do
Class.new(Gitlab::Ci::Status::Core)
.new(pipeline).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}"
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Factory do
subject do
described_class.new(pipeline)
end
let(:status) do
subject.fabricate!
end
context 'when pipeline has a core status' do
HasStatus::AVAILABLE_STATUSES.each do |core_status|
context "when core status is #{core_status}" do
let(:pipeline) do
create(:ci_pipeline, status: core_status)
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 pipeline methods' do
expect(status).to have_details
expect(status).not_to have_action
expect(status.details_path)
.to include "pipelines/#{pipeline.id}"
end
end
end
end
context 'when pipeline has warnings' do
let(:pipeline) do
create(:ci_pipeline, status: :success)
end
before do
create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline)
end
it 'fabricates extended "success with warnings" status' do
expect(status)
.to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings
end
it 'extends core status with common pipeline methods' do
expect(status).to have_details
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
subject do
described_class.new(double('status'))
end
describe '#test' do
it { expect(subject.text).to eq 'passed' }
end
describe '#label' do
it { expect(subject.label).to eq 'passed with warnings' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_warning' }
end
describe '.matches?' do
context 'when pipeline is successful' do
let(:pipeline) do
create(:ci_pipeline, status: :success)
end
context 'when pipeline has warnings' do
before do
allow(pipeline).to receive(:has_warnings?).and_return(true)
end
it 'is a correct match' do
expect(described_class.matches?(pipeline)).to eq true
end
end
context 'when pipeline does not have warnings' do
it 'does not match' do
expect(described_class.matches?(pipeline)).to eq false
end
end
end
context 'when pipeline is not successful' do
let(:pipeline) do
create(:ci_pipeline, status: :skipped)
end
context 'when pipeline has warnings' do
before do
allow(pipeline).to receive(:has_warnings?).and_return(true)
end
it 'does not match' do
expect(described_class.matches?(pipeline)).to eq false
end
end
context 'when pipeline does not have warnings' do
it 'does not match' do
expect(described_class.matches?(pipeline)).to eq false
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Running do
subject { described_class.new(double('subject')) }
describe '#text' do
it { expect(subject.label).to eq 'running' }
end
describe '#label' do
it { expect(subject.label).to eq 'running' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_running' }
end
describe '#title' do
it { expect(subject.title).to eq 'Double: running' }
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Skipped do
subject { described_class.new(double('subject')) }
describe '#text' do
it { expect(subject.label).to eq 'skipped' }
end
describe '#label' do
it { expect(subject.label).to eq 'skipped' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_skipped' }
end
describe '#title' do
it { expect(subject.title).to eq 'Double: skipped' }
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Success do
subject { described_class.new(double('subject')) }
describe '#text' do
it { expect(subject.label).to eq 'passed' }
end
describe '#label' do
it { expect(subject.label).to eq 'passed' }
end
describe '#icon' do
it { expect(subject.icon).to eq 'icon_status_success' }
end
describe '#title' do
it { expect(subject.title).to eq 'Double: passed' }
end
end
...@@ -404,6 +404,76 @@ describe Ci::Pipeline, models: true do ...@@ -404,6 +404,76 @@ describe Ci::Pipeline, models: true do
end end
end end
describe '#detailed_status' do
context 'when pipeline is created' do
let(:pipeline) { create(:ci_pipeline, status: :created) }
it 'returns detailed status for created pipeline' do
expect(pipeline.detailed_status.text).to eq 'created'
end
end
context 'when pipeline is pending' do
let(:pipeline) { create(:ci_pipeline, status: :pending) }
it 'returns detailed status for pending pipeline' do
expect(pipeline.detailed_status.text).to eq 'pending'
end
end
context 'when pipeline is running' do
let(:pipeline) { create(:ci_pipeline, status: :running) }
it 'returns detailed status for running pipeline' do
expect(pipeline.detailed_status.text).to eq 'running'
end
end
context 'when pipeline is successful' do
let(:pipeline) { create(:ci_pipeline, status: :success) }
it 'returns detailed status for successful pipeline' do
expect(pipeline.detailed_status.text).to eq 'passed'
end
end
context 'when pipeline is failed' do
let(:pipeline) { create(:ci_pipeline, status: :failed) }
it 'returns detailed status for failed pipeline' do
expect(pipeline.detailed_status.text).to eq 'failed'
end
end
context 'when pipeline is canceled' do
let(:pipeline) { create(:ci_pipeline, status: :canceled) }
it 'returns detailed status for canceled pipeline' do
expect(pipeline.detailed_status.text).to eq 'canceled'
end
end
context 'when pipeline is skipped' do
let(:pipeline) { create(:ci_pipeline, status: :skipped) }
it 'returns detailed status for skipped pipeline' do
expect(pipeline.detailed_status.text).to eq 'skipped'
end
end
context 'when pipeline is successful but with warnings' do
let(:pipeline) { create(:ci_pipeline, status: :success) }
before do
create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline)
end
it 'retruns detailed status for successful pipeline with warnings' do
expect(pipeline.detailed_status.label).to eq 'passed with warnings'
end
end
end
describe '#cancelable?' do describe '#cancelable?' do
%i[created running pending].each do |status0| %i[created running pending].each do |status0|
context "when there is a build #{status0}" do context "when there is a build #{status0}" 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