Commit ae63f152 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'feature/svg-badge-template' into 'master'

Use badge image template instead of using separate images

## What does this MR do?

Makes it possible to use template for badge instead of having multiple files.

## Are there points in the code the reviewer needs to double check?

We also have a deprecated badge in `controllers/ci/projects_controller.rb`.  We decided to leave it until 9.0, so we still have images in `public/ci/` until 9.0.

## Why was this MR needed?

We are going to implement build coverage badge, and we do not want to store 101 SVG images for each percentage value.

## What are the relevant issue numbers?

#3714

## Screenshots (if relevant)

![new_build_badge](/uploads/f1d4ed5e34278eb01f48994b5b0579f1/new_build_badge.png)

## Does this MR meet the acceptance criteria?

- [ ] ~~[CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added~~ (refactoring)
- [ ] ~~[Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)~~
- [ ] ~~API support added~~
- Tests
  - [x] Added for this feature/bug
  - [x] All builds are passing
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [ ] ~~[Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)~~ (refactoring)

See merge request !5520
parents 34d5426f 3756bbe1
...@@ -8,8 +8,9 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -8,8 +8,9 @@ class Projects::BadgesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { render_404 } format.html { render_404 }
format.svg do format.svg do
send_data(badge.data, type: badge.type, disposition: 'inline') render 'badge', locals: { badge: badge.template }
end end
end end
end end
......
...@@ -3,7 +3,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -3,7 +3,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def show def show
@ref = params[:ref] || @project.default_branch || 'master' @ref = params[:ref] || @project.default_branch || 'master'
@build_badge = Gitlab::Badge::Build.new(@project, @ref) @build_badge = Gitlab::Badge::Build.new(@project, @ref).metadata
end end
def update def update
......
<svg xmlns="http://www.w3.org/2000/svg" width="<%= badge.width %>" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="a">
<rect width="<%= badge.width %>" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#a)">
<path fill="<%= badge.key_color %>"
d="M0 0 h<%= badge.key_width %> v20 H0 z"/>
<path fill="<%= badge.value_color %>"
d="M<%= badge.key_width %> 0 h<%= badge.value_width %> v20 H<%= badge.key_width %> z"/>
<path fill="url(#b)"
d="M0 0 h<%= badge.width %> v20 H0 z"/>
</g>
<g fill="#fff" text-anchor="middle">
<g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="<%= badge.key_text_anchor %>" y="15" fill="#010101" fill-opacity=".3">
<%= badge.key_text %>
</text>
<text x="<%= badge.key_text_anchor %>" y="14">
<%= badge.key_text %>
</text>
<text x="<%= badge.value_text_anchor %>" y="15" fill="#010101" fill-opacity=".3">
<%= badge.value_text %>
</text>
<text x="<%= badge.value_text_anchor %>" y="14">
<%= badge.value_text %>
</text>
</g>
</g>
</svg>
...@@ -26,7 +26,7 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps ...@@ -26,7 +26,7 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
def expect_badge(status) def expect_badge(status)
svg = Nokogiri::XML.parse(page.body) svg = Nokogiri::XML.parse(page.body)
expect(page.response_headers).to include('Content-Type' => 'image/svg+xml') expect(page.response_headers['Content-Type']).to include('image/svg+xml')
expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
end end
end end
...@@ -4,42 +4,26 @@ module Gitlab ...@@ -4,42 +4,26 @@ module Gitlab
# Build badge # Build badge
# #
class Build class Build
include Gitlab::Application.routes.url_helpers delegate :key_text, :value_text, to: :template
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::UrlHelper
def initialize(project, ref) def initialize(project, ref)
@project, @ref = project, ref @project = project
@image = ::Ci::ImageForBuildService.new.execute(project, ref: ref) @ref = ref
@sha = @project.commit(@ref).try(:sha)
end end
def type def status
'image/svg+xml' @project.pipelines
.where(sha: @sha, ref: @ref)
.status || 'unknown'
end end
def data def metadata
File.read(@image[:path]) @metadata ||= Build::Metadata.new(@project, @ref)
end end
def to_s def template
@image[:name].sub(/\.svg$/, '') @template ||= Build::Template.new(status)
end
def to_html
link_to(image_tag(image_url, alt: 'build status'), link_url)
end
def to_markdown
"[![build status](#{image_url})](#{link_url})"
end
def image_url
build_namespace_project_badges_url(@project.namespace,
@project, @ref, format: :svg)
end
def link_url
namespace_project_commits_url(@project.namespace, @project, id: @ref)
end end
end end
end end
......
module Gitlab
module Badge
class Build
##
# Class that describes build badge metadata
#
class Metadata
include Gitlab::Application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::UrlHelper
def initialize(project, ref)
@project = project
@ref = ref
end
def to_html
link_to(image_tag(image_url, alt: 'build status'), link_url)
end
def to_markdown
"[![build status](#{image_url})](#{link_url})"
end
def image_url
build_namespace_project_badges_url(@project.namespace,
@project, @ref, format: :svg)
end
def link_url
namespace_project_commits_url(@project.namespace, @project, id: @ref)
end
end
end
end
end
module Gitlab
module Badge
class Build
##
# Class that represents a build badge template.
#
# Template object will be passed to badge.svg.erb template.
#
class Template
STATUS_COLOR = {
success: '#4c1',
failed: '#e05d44',
running: '#dfb317',
pending: '#dfb317',
canceled: '#9f9f9f',
skipped: '#9f9f9f',
unknown: '#9f9f9f'
}
def initialize(status)
@status = status
end
def key_text
'build'
end
def value_text
@status
end
def key_width
38
end
def value_width
54
end
def key_color
'#555'
end
def value_color
STATUS_COLOR[@status.to_sym] ||
STATUS_COLOR[:unknown]
end
def key_text_anchor
key_width / 2
end
def value_text_anchor
key_width + (value_width / 2)
end
def width
key_width + value_width
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Badge::Build::Metadata do
let(:project) { create(:project) }
let(:branch) { 'master' }
let(:badge) { described_class.new(project, branch) }
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(badge.to_html) }
let(:a_href) { html.at('a') }
it 'points to link' do
expect(a_href[:href]).to eq badge.link_url
end
it 'contains clickable image' do
expect(a_href.children.first.name).to eq 'img'
end
end
describe '#to_markdown' do
subject { badge.to_markdown }
it { is_expected.to include badge.image_url }
it { is_expected.to include badge.link_url }
end
describe '#image_url' do
subject { badge.image_url }
it { is_expected.to include "badges/#{branch}/build.svg" }
end
describe '#link_url' do
subject { badge.link_url }
it { is_expected.to include "commits/#{branch}" }
end
end
require 'spec_helper'
describe Gitlab::Badge::Build::Template do
let(:status) { 'success' }
let(:template) { described_class.new(status) }
describe '#key_text' do
it 'is always says build' do
expect(template.key_text).to eq 'build'
end
end
describe '#value_text' do
it 'is status value' do
expect(template.value_text).to eq 'success'
end
end
describe 'widths and text anchors' do
it 'has fixed width and text anchors' do
expect(template.width).to eq 92
expect(template.key_width).to eq 38
expect(template.value_width).to eq 54
expect(template.key_text_anchor).to eq 19
expect(template.value_text_anchor).to eq 65
end
end
describe '#key_color' do
it 'is always the same' do
expect(template.key_color).to eq '#555'
end
end
describe '#value_color' do
context 'when status is success' do
let(:status) { 'success' }
it 'has expected color' do
expect(template.value_color).to eq '#4c1'
end
end
context 'when status is failed' do
let(:status) { 'failed' }
it 'has expected color' do
expect(template.value_color).to eq '#e05d44'
end
end
context 'when status is running' do
let(:status) { 'running' }
it 'has expected color' do
expect(template.value_color).to eq '#dfb317'
end
end
context 'when status is unknown' do
let(:status) { 'unknown' }
it 'has expected color' do
expect(template.value_color).to eq '#9f9f9f'
end
end
context 'when status does not match any known statuses' do
let(:status) { 'invalid status' }
it 'has expected color' do
expect(template.value_color).to eq '#9f9f9f'
end
end
end
end
...@@ -6,39 +6,17 @@ describe Gitlab::Badge::Build do ...@@ -6,39 +6,17 @@ describe Gitlab::Badge::Build do
let(:branch) { 'master' } let(:branch) { 'master' }
let(:badge) { described_class.new(project, branch) } let(:badge) { described_class.new(project, branch) }
describe '#type' do describe '#metadata' do
subject { badge.type } it 'returns badge metadata' do
it { is_expected.to eq 'image/svg+xml' } expect(badge.metadata.image_url)
end .to include 'badges/master/build.svg'
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(badge.to_html) }
let(:a_href) { html.at('a') }
it 'points to link' do
expect(a_href[:href]).to eq badge.link_url
end
it 'contains clickable image' do
expect(a_href.children.first.name).to eq 'img'
end end
end end
describe '#to_markdown' do describe '#key_text' do
subject { badge.to_markdown } it 'always says build' do
expect(badge.key_text).to eq 'build'
it { is_expected.to include badge.image_url } end
it { is_expected.to include badge.link_url }
end
describe '#image_url' do
subject { badge.image_url }
it { is_expected.to include "badges/#{branch}/build.svg" }
end
describe '#link_url' do
subject { badge.link_url }
it { is_expected.to include "commits/#{branch}" }
end end
context 'build exists' do context 'build exists' do
...@@ -47,16 +25,15 @@ describe Gitlab::Badge::Build do ...@@ -47,16 +25,15 @@ describe Gitlab::Badge::Build do
context 'build success' do context 'build success' do
before { build.success! } before { build.success! }
describe '#to_s' do describe '#status' do
subject { badge.to_s } it 'is successful' do
it { is_expected.to eq 'build-success' } expect(badge.status).to eq 'success'
end
end end
describe '#data' do describe '#value_text' do
let(:data) { badge.data } it 'returns correct value text' do
expect(badge.value_text).to eq 'success'
it 'contains information about success' do
expect(status_node(data, 'success')).to be_truthy
end end
end end
end end
...@@ -64,47 +41,57 @@ describe Gitlab::Badge::Build do ...@@ -64,47 +41,57 @@ describe Gitlab::Badge::Build do
context 'build failed' do context 'build failed' do
before { build.drop! } before { build.drop! }
describe '#to_s' do describe '#status' do
subject { badge.to_s } it 'failed' do
it { is_expected.to eq 'build-failed' } expect(badge.status).to eq 'failed'
end
end end
describe '#data' do describe '#value_text' do
let(:data) { badge.data } it 'has correct value text' do
expect(badge.value_text).to eq 'failed'
it 'contains information about failure' do
expect(status_node(data, 'failed')).to be_truthy
end end
end end
end end
end
context 'build does not exist' do context 'when outdated pipeline for given ref exists' do
describe '#to_s' do before do
subject { badge.to_s } build.success!
it { is_expected.to eq 'build-unknown' }
old_build = create_build(project, '11eeffdd', branch)
old_build.drop!
end
it 'does not take outdated pipeline into account' do
expect(badge.status).to eq 'success'
end
end end
describe '#data' do context 'when multiple pipelines exist for given sha' do
let(:data) { badge.data } before do
build.drop!
new_build = create_build(project, sha, branch)
new_build.success!
end
it 'contains infromation about unknown build' do it 'reports the compound status' do
expect(status_node(data, 'unknown')).to be_truthy expect(badge.status).to eq 'failed'
end end
end end
end end
context 'when outdated pipeline for given ref exists' do context 'build does not exist' do
before do describe '#status' do
build = create_build(project, sha, branch) it 'is unknown' do
build.success! expect(badge.status).to eq 'unknown'
end
old_build = create_build(project, '11eeffdd', branch)
old_build.drop!
end end
it 'does not take outdated pipeline into account' do describe '#value_text' do
expect(badge.to_s).to eq 'build-success' it 'has correct value text' do
expect(badge.value_text).to eq 'unknown'
end
end end
end end
...@@ -115,9 +102,4 @@ describe Gitlab::Badge::Build do ...@@ -115,9 +102,4 @@ describe Gitlab::Badge::Build do
create(:ci_build, pipeline: pipeline, stage: 'notify') create(:ci_build, pipeline: pipeline, stage: 'notify')
end end
def status_node(data, status)
xml = Nokogiri::XML.parse(data)
xml.at(%Q{text:contains("#{status}")})
end
end end
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