Merge branch 'fix/external-status-badge-links' into 'master'
Link external commit status badges to target URLs Closes #25662 See merge request !8611
This commit is contained in:
commit
046e0bd6e7
10 changed files with 220 additions and 30 deletions
|
@ -1,6 +1,10 @@
|
||||||
class GenericCommitStatus < CommitStatus
|
class GenericCommitStatus < CommitStatus
|
||||||
before_validation :set_default_values
|
before_validation :set_default_values
|
||||||
|
|
||||||
|
validates :target_url, addressable_url: true,
|
||||||
|
length: { maximum: 255 },
|
||||||
|
allow_nil: true
|
||||||
|
|
||||||
# GitHub compatible API
|
# GitHub compatible API
|
||||||
alias_attribute :context, :name
|
alias_attribute :context, :name
|
||||||
|
|
||||||
|
@ -12,4 +16,10 @@ class GenericCommitStatus < CommitStatus
|
||||||
def tags
|
def tags
|
||||||
[:external]
|
[:external]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def detailed_status(current_user)
|
||||||
|
Gitlab::Ci::Status::External::Factory
|
||||||
|
.new(self, current_user)
|
||||||
|
.fabricate!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Link external build badge to its target URL
|
||||||
|
merge_request: 8611
|
||||||
|
author:
|
|
@ -78,6 +78,8 @@ module API
|
||||||
description: params[:description]
|
description: params[:description]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
render_validation_error!(status) if status.invalid?
|
||||||
|
|
||||||
begin
|
begin
|
||||||
case params[:state]
|
case params[:state]
|
||||||
when 'pending'
|
when 'pending'
|
||||||
|
|
22
lib/gitlab/ci/status/external/common.rb
vendored
Normal file
22
lib/gitlab/ci/status/external/common.rb
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
module Gitlab
|
||||||
|
module Ci
|
||||||
|
module Status
|
||||||
|
module External
|
||||||
|
module Common
|
||||||
|
def has_details?
|
||||||
|
subject.target_url.present? &&
|
||||||
|
can?(user, :read_commit_status, subject)
|
||||||
|
end
|
||||||
|
|
||||||
|
def details_path
|
||||||
|
subject.target_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_action?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
lib/gitlab/ci/status/external/factory.rb
vendored
Normal file
13
lib/gitlab/ci/status/external/factory.rb
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module Gitlab
|
||||||
|
module Ci
|
||||||
|
module Status
|
||||||
|
module External
|
||||||
|
class Factory < Status::Factory
|
||||||
|
def self.common_helpers
|
||||||
|
Status::External::Common
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,18 +11,42 @@ describe 'Pipeline', :feature, :js do
|
||||||
project.team << [user, :developer]
|
project.team << [user, :developer]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_context 'pipeline builds' do
|
||||||
|
let!(:build_passed) do
|
||||||
|
create(:ci_build, :success,
|
||||||
|
pipeline: pipeline, stage: 'build', name: 'build')
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:build_failed) do
|
||||||
|
create(:ci_build, :failed,
|
||||||
|
pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:build_running) do
|
||||||
|
create(:ci_build, :running,
|
||||||
|
pipeline: pipeline, stage: 'deploy', name: 'deploy')
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:build_manual) do
|
||||||
|
create(:ci_build, :manual,
|
||||||
|
pipeline: pipeline, stage: 'deploy', name: 'manual-build')
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:build_external) do
|
||||||
|
create(:generic_commit_status, status: 'success',
|
||||||
|
pipeline: pipeline,
|
||||||
|
name: 'jenkins',
|
||||||
|
stage: 'external',
|
||||||
|
target_url: 'http://gitlab.com/status')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET /:project/pipelines/:id' do
|
describe 'GET /:project/pipelines/:id' do
|
||||||
|
include_context 'pipeline builds'
|
||||||
|
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
|
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
|
||||||
|
|
||||||
before do
|
|
||||||
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
|
|
||||||
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
|
|
||||||
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
|
|
||||||
@manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build')
|
|
||||||
@external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
|
|
||||||
end
|
|
||||||
|
|
||||||
before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
|
before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
|
||||||
|
|
||||||
it 'shows the pipeline graph' do
|
it 'shows the pipeline graph' do
|
||||||
|
@ -116,6 +140,7 @@ describe 'Pipeline', :feature, :js do
|
||||||
it 'shows the success icon and the generic comit status build' do
|
it 'shows the success icon and the generic comit status build' do
|
||||||
expect(page).to have_selector('.ci-status-icon-success')
|
expect(page).to have_selector('.ci-status-icon-success')
|
||||||
expect(page).to have_content('jenkins')
|
expect(page).to have_content('jenkins')
|
||||||
|
expect(page).to have_link('jenkins', href: 'http://gitlab.com/status')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -157,26 +182,22 @@ describe 'Pipeline', :feature, :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /:project/pipelines/:id/builds' do
|
describe 'GET /:project/pipelines/:id/builds' do
|
||||||
|
include_context 'pipeline builds'
|
||||||
|
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
|
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
|
visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)
|
||||||
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
|
|
||||||
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
|
|
||||||
@manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build')
|
|
||||||
@external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
before { visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)}
|
|
||||||
|
|
||||||
it 'shows a list of builds' do
|
it 'shows a list of builds' do
|
||||||
expect(page).to have_content('Test')
|
expect(page).to have_content('Test')
|
||||||
expect(page).to have_content(@success.id)
|
expect(page).to have_content(build_passed.id)
|
||||||
expect(page).to have_content('Deploy')
|
expect(page).to have_content('Deploy')
|
||||||
expect(page).to have_content(@failed.id)
|
expect(page).to have_content(build_failed.id)
|
||||||
expect(page).to have_content(@running.id)
|
expect(page).to have_content(build_running.id)
|
||||||
expect(page).to have_content(@external.id)
|
expect(page).to have_content(build_external.id)
|
||||||
expect(page).to have_content('Retry failed')
|
expect(page).to have_content('Retry failed')
|
||||||
expect(page).to have_content('Cancel running')
|
expect(page).to have_content('Cancel running')
|
||||||
expect(page).to have_link('Play')
|
expect(page).to have_link('Play')
|
||||||
|
@ -230,7 +251,7 @@ describe 'Pipeline', :feature, :js do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(@manual.reload).to be_pending }
|
it { expect(build_manual.reload).to be_pending }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
39
spec/lib/gitlab/ci/status/external/common_spec.rb
vendored
Normal file
39
spec/lib/gitlab/ci/status/external/common_spec.rb
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::Ci::Status::External::Common do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:project) { external_status.project }
|
||||||
|
let(:external_target_url) { 'http://example.gitlab.com/status' }
|
||||||
|
|
||||||
|
let(:external_status) do
|
||||||
|
create(:generic_commit_status, target_url: external_target_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject do
|
||||||
|
Gitlab::Ci::Status::Core
|
||||||
|
.new(external_status, 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 commit status' do
|
||||||
|
before { project.team << [user, :developer] }
|
||||||
|
|
||||||
|
it { is_expected.to have_details }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user does not have access to read commit status' do
|
||||||
|
it { is_expected.not_to have_details }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#details_path' do
|
||||||
|
it 'links to the external target URL' do
|
||||||
|
expect(subject.details_path).to eq external_target_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
38
spec/lib/gitlab/ci/status/external/factory_spec.rb
vendored
Normal file
38
spec/lib/gitlab/ci/status/external/factory_spec.rb
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::Ci::Status::External::Factory do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:project) { resource.project }
|
||||||
|
let(:status) { factory.fabricate! }
|
||||||
|
let(:factory) { described_class.new(resource, user) }
|
||||||
|
let(:external_url) { 'http://gitlab.com/status' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.team << [user, :developer]
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when external status has a simple core status' do
|
||||||
|
HasStatus::AVAILABLE_STATUSES.each do |simple_status|
|
||||||
|
context "when core status is #{simple_status}" do
|
||||||
|
let(:resource) do
|
||||||
|
create(:generic_commit_status, status: simple_status,
|
||||||
|
target_url: external_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_status) do
|
||||||
|
Gitlab::Ci::Status.const_get(simple_status.capitalize)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fabricates a core status #{simple_status}" do
|
||||||
|
expect(status).to be_a expected_status
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'extends core status with common methods' do
|
||||||
|
expect(status).to have_details
|
||||||
|
expect(status).not_to have_action
|
||||||
|
expect(status.details_path).to eq external_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,10 +1,20 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe GenericCommitStatus, models: true do
|
describe GenericCommitStatus, models: true do
|
||||||
let(:pipeline) { create(:ci_pipeline) }
|
let(:project) { create(:empty_project) }
|
||||||
|
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||||
|
let(:external_url) { 'http://example.gitlab.com/status' }
|
||||||
|
|
||||||
let(:generic_commit_status) do
|
let(:generic_commit_status) do
|
||||||
create(:generic_commit_status, pipeline: pipeline)
|
create(:generic_commit_status, pipeline: pipeline,
|
||||||
|
target_url: external_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
it { is_expected.to validate_length_of(:target_url).is_at_most(255) }
|
||||||
|
it { is_expected.to allow_value(nil).for(:target_url) }
|
||||||
|
it { is_expected.to allow_value('http://gitlab.com/s').for(:target_url) }
|
||||||
|
it { is_expected.not_to allow_value('javascript:alert(1)').for(:target_url) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#context' do
|
describe '#context' do
|
||||||
|
@ -22,10 +32,25 @@ describe GenericCommitStatus, models: true do
|
||||||
|
|
||||||
describe '#detailed_status' do
|
describe '#detailed_status' do
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
let(:status) { generic_commit_status.detailed_status(user) }
|
||||||
|
|
||||||
it 'returns detailed status object' do
|
it 'returns detailed status object' do
|
||||||
expect(generic_commit_status.detailed_status(user))
|
expect(status).to be_a Gitlab::Ci::Status::Success
|
||||||
.to be_a Gitlab::Ci::Status::Success
|
end
|
||||||
|
|
||||||
|
context 'when user has ability to see datails' do
|
||||||
|
before { project.team << [user, :developer] }
|
||||||
|
|
||||||
|
it 'details path points to an external URL' do
|
||||||
|
expect(status).to have_details
|
||||||
|
expect(status.details_path).to eq external_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user should not see details' do
|
||||||
|
it 'does not have details' do
|
||||||
|
expect(status).not_to have_details
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -152,8 +152,11 @@ describe API::CommitStatuses, api: true do
|
||||||
|
|
||||||
context 'with all optional parameters' do
|
context 'with all optional parameters' do
|
||||||
before do
|
before do
|
||||||
optional_params = { state: 'success', context: 'coverage',
|
optional_params = { state: 'success',
|
||||||
ref: 'develop', target_url: 'url', description: 'test' }
|
context: 'coverage',
|
||||||
|
ref: 'develop',
|
||||||
|
description: 'test',
|
||||||
|
target_url: 'http://gitlab.com/status' }
|
||||||
|
|
||||||
post api(post_url, developer), optional_params
|
post api(post_url, developer), optional_params
|
||||||
end
|
end
|
||||||
|
@ -164,12 +167,12 @@ describe API::CommitStatuses, api: true do
|
||||||
expect(json_response['status']).to eq('success')
|
expect(json_response['status']).to eq('success')
|
||||||
expect(json_response['name']).to eq('coverage')
|
expect(json_response['name']).to eq('coverage')
|
||||||
expect(json_response['ref']).to eq('develop')
|
expect(json_response['ref']).to eq('develop')
|
||||||
expect(json_response['target_url']).to eq('url')
|
|
||||||
expect(json_response['description']).to eq('test')
|
expect(json_response['description']).to eq('test')
|
||||||
|
expect(json_response['target_url']).to eq('http://gitlab.com/status')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'invalid status' do
|
context 'when status is invalid' do
|
||||||
before { post api(post_url, developer), state: 'invalid' }
|
before { post api(post_url, developer), state: 'invalid' }
|
||||||
|
|
||||||
it 'does not create commit status' do
|
it 'does not create commit status' do
|
||||||
|
@ -177,7 +180,7 @@ describe API::CommitStatuses, api: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'request without state' do
|
context 'when request without a state made' do
|
||||||
before { post api(post_url, developer) }
|
before { post api(post_url, developer) }
|
||||||
|
|
||||||
it 'does not create commit status' do
|
it 'does not create commit status' do
|
||||||
|
@ -185,7 +188,7 @@ describe API::CommitStatuses, api: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'invalid commit' do
|
context 'when commit SHA is invalid' do
|
||||||
let(:sha) { 'invalid_sha' }
|
let(:sha) { 'invalid_sha' }
|
||||||
before { post api(post_url, developer), state: 'running' }
|
before { post api(post_url, developer), state: 'running' }
|
||||||
|
|
||||||
|
@ -193,6 +196,19 @@ describe API::CommitStatuses, api: true do
|
||||||
expect(response).to have_http_status(404)
|
expect(response).to have_http_status(404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when target URL is an invalid address' do
|
||||||
|
before do
|
||||||
|
post api(post_url, developer), state: 'pending',
|
||||||
|
target_url: 'invalid url'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds with bad request status and validation errors' do
|
||||||
|
expect(response).to have_http_status(400)
|
||||||
|
expect(json_response['message']['target_url'])
|
||||||
|
.to include 'must be a valid URL'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'reporter user' do
|
context 'reporter user' do
|
||||||
|
|
Loading…
Reference in a new issue