Commit 32d31e9a authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'release-badge' into 'master'

Built in badge for latest release

See merge request gitlab-org/gitlab!78899
parents 84a5101e 091a55c1
...@@ -8,6 +8,7 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -8,6 +8,7 @@ class Projects::BadgesController < Projects::ApplicationController
feature_category :continuous_integration, [:index, :pipeline] feature_category :continuous_integration, [:index, :pipeline]
feature_category :code_testing, [:coverage] feature_category :code_testing, [:coverage]
feature_category :release_orchestration, [:release]
def pipeline def pipeline
pipeline_status = Gitlab::Ci::Badge::Pipeline::Status pipeline_status = Gitlab::Ci::Badge::Pipeline::Status
...@@ -34,6 +35,17 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -34,6 +35,17 @@ class Projects::BadgesController < Projects::ApplicationController
render_badge coverage_report render_badge coverage_report
end end
def release
latest_release = Gitlab::Ci::Badge::Release::LatestRelease
.new(project, current_user, opts: {
key_text: params[:key_text],
key_width: params[:key_width],
order_by: params[:order_by]
})
render_badge latest_release
end
private private
def badge_layout def badge_layout
......
...@@ -160,6 +160,8 @@ module Projects ...@@ -160,6 +160,8 @@ module Projects
@badges.map! do |badge| @badges.map! do |badge|
badge.new(@project, @ref).metadata badge.new(@project, @ref).metadata
end end
@badges.append(Gitlab::Ci::Badge::Release::LatestRelease.new(@project, current_user).metadata)
end end
def define_auto_devops_variables def define_auto_devops_variables
......
...@@ -467,6 +467,14 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -467,6 +467,14 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
get :planning_hierarchy get :planning_hierarchy
resources :badges, only: [] do
collection do
constraints format: /svg/ do
get :release
end
end
end
end end
# End of the /-/ scope. # End of the /-/ scope.
......
# frozen_string_literal: true
module Gitlab::Ci
module Badge
module Release
class LatestRelease < Badge::Base
attr_reader :project, :release, :customization
def initialize(project, current_user, opts: {})
@project = project
@customization = {
key_width: opts[:key_width] ? opts[:key_width].to_i : nil,
key_text: opts[:key_text]
}
# In the future, we should support `order_by=semver` for showing the
# latest release based on Semantic Versioning.
@release = ::ReleasesFinder.new(
project,
current_user,
order_by: opts[:order_by]).execute.first
end
def entity
'Latest Release'
end
def tag
@release&.tag
end
def metadata
@metadata ||= Release::Metadata.new(self)
end
def template
@template ||= Release::Template.new(self)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab::Ci
module Badge
module Release
class Metadata < Badge::Metadata
def initialize(badge)
@project = badge.project
end
def title
'Latest Release'
end
def image_url
release_project_badges_url(@project, format: :svg)
end
def link_url
project_releases_url(@project)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab::Ci
module Badge
module Release
# Template object will be passed to badge.svg.erb template.
class Template < Badge::Template
STATUS_COLOR = {
latest: '#3076af',
none: '#e05d44'
}.freeze
KEY_WIDTH_DEFAULT = 90
VALUE_WIDTH_DEFAULT = 54
def initialize(badge)
@entity = badge.entity
@tag = badge.tag || "none"
@key_width = badge.customization.dig(:key_width)
@key_text = badge.customization.dig(:key_text)
end
def key_text
@key_text || @entity.to_s
end
def key_width
@key_width || KEY_WIDTH_DEFAULT
end
def value_text
@tag.to_s
end
def value_width
VALUE_WIDTH_DEFAULT
end
def value_color
STATUS_COLOR[@tag.to_sym] || STATUS_COLOR[:latest]
end
end
end
end
end
...@@ -7,39 +7,100 @@ RSpec.describe Projects::BadgesController do ...@@ -7,39 +7,100 @@ RSpec.describe Projects::BadgesController do
let_it_be(:pipeline, reload: true) { create(:ci_empty_pipeline, project: project) } let_it_be(:pipeline, reload: true) { create(:ci_empty_pipeline, project: project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
shared_examples 'a badge resource' do |badge_type| shared_context 'renders badge irrespective of project access levels' do |badge_type|
context 'when pipelines are public' do context 'when project is public' do
before do before do
project.update!(public_builds: true) project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end end
context 'when project is public' do it "returns the #{badge_type} badge to unauthenticated users" do
before do get_badge(badge_type)
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
it "returns the #{badge_type} badge to unauthenticated users" do expect(response).to have_gitlab_http_status(:ok)
get_badge(badge_type) end
end
expect(response).to have_gitlab_http_status(:ok) context 'when project is restricted' do
end before do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
project.add_guest(user)
sign_in(user)
end end
context 'when project is restricted' do it "returns the #{badge_type} badge to guest users" do
before do get_badge(badge_type)
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
project.add_guest(user)
sign_in(user)
end
it "returns the #{badge_type} badge to guest users" do expect(response).to have_gitlab_http_status(:ok)
get_badge(badge_type) end
end
end
expect(response).to have_gitlab_http_status(:ok) shared_context 'when pipelines are public' do |badge_type|
end before do
project.update!(public_builds: true)
end
it_behaves_like 'renders badge irrespective of project access levels', badge_type
end
shared_context 'when pipelines are not public' do |badge_type|
before do
project.update!(public_builds: false)
end
context 'when project is public' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
it 'returns 404 to unauthenticated users' do
get_badge(badge_type)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when project is restricted to the user' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
project.add_guest(user)
sign_in(user)
end
it 'defaults to project permissions' do
get_badge(badge_type)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
shared_context 'customization' do |badge_type|
render_views
before do
project.add_maintainer(user)
sign_in(user)
end
context 'when key_text param is used' do
it 'sets custom key text' do
get_badge(badge_type, key_text: 'custom key text')
expect(response.body).to include('custom key text')
end
end
context 'when key_width param is used' do
it 'sets custom key width' do
get_badge(badge_type, key_width: '123')
expect(response.body).to include('123')
end end
end end
end
shared_examples 'a badge resource' do |badge_type|
context 'format' do context 'format' do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -77,61 +138,11 @@ RSpec.describe Projects::BadgesController do ...@@ -77,61 +138,11 @@ RSpec.describe Projects::BadgesController do
end end
end end
context 'when pipelines are not public' do it_behaves_like 'customization', badge_type
before do
project.update!(public_builds: false)
end
context 'when project is public' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
it 'returns 404 to unauthenticated users' do
get_badge(badge_type)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when project is restricted to the user' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
project.add_guest(user)
sign_in(user)
end
it 'defaults to project permissions' do
get_badge(badge_type)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'customization' do
render_views
before do
project.add_maintainer(user)
sign_in(user)
end
context 'when key_text param is used' do
it 'sets custom key text' do
get_badge(badge_type, key_text: 'custom key text')
expect(response.body).to include('custom key text')
end
end
context 'when key_width param is used' do
it 'sets custom key width' do
get_badge(badge_type, key_width: '123')
expect(response.body).to include('123') if [:pipeline, :coverage].include?(badge_type)
end it_behaves_like 'when pipelines are public', badge_type
end it_behaves_like 'when pipelines are not public', badge_type
end end
end end
...@@ -163,6 +174,13 @@ RSpec.describe Projects::BadgesController do ...@@ -163,6 +174,13 @@ RSpec.describe Projects::BadgesController do
it_behaves_like 'a badge resource', :coverage it_behaves_like 'a badge resource', :coverage
end end
describe '#release' do
action = :release
it_behaves_like 'a badge resource', action
it_behaves_like 'renders badge irrespective of project access levels', action
end
def get_badge(badge, args = {}) def get_badge(badge, args = {})
params = { params = {
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Badge::Release::LatestRelease do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
project.add_guest(user)
create(:release, project: project, released_at: 1.day.ago)
end
subject { described_class.new(project, user) }
describe '#entity' do
it 'describes latest release' do
expect(subject.entity).to eq 'Latest Release'
end
end
describe '#tag' do
it 'returns latest release tag for the project ordered using release_at' do
create(:release, tag: "v1.0.0", project: project, released_at: 1.hour.ago)
latest_release = create(:release, tag: "v2.0.0", project: project, released_at: Time.current)
expect(subject.tag).to eq latest_release.tag
end
end
describe '#metadata' do
it 'returns correct metadata' do
expect(subject.metadata.image_url).to include 'release.svg'
end
end
describe '#template' do
it 'returns correct template' do
expect(subject.template.key_text).to eq 'Latest Release'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require 'lib/gitlab/ci/badge/shared/metadata'
RSpec.describe Gitlab::Ci::Badge::Release::Metadata do
let(:project) { create(:project) }
let(:ref) { 'feature' }
let!(:release) { create(:release, tag: ref, project: project) }
let(:user) { create(:user) }
let(:badge) do
Gitlab::Ci::Badge::Release::LatestRelease.new(project, user)
end
let(:metadata) { described_class.new(badge) }
before do
project.add_guest(user)
end
it_behaves_like 'badge metadata'
describe '#title' do
it 'returns latest release title' do
expect(metadata.title).to eq 'Latest Release'
end
end
describe '#image_url' do
it 'returns valid url' do
expect(metadata.image_url).to include "/-/badges/release.svg"
end
end
describe '#link_url' do
it 'returns valid link' do
expect(metadata.link_url).to include "/-/releases"
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Badge::Release::Template do
let(:project) { create(:project) }
let(:ref) { 'v1.2.3' }
let(:user) { create(:user) }
let!(:release) { create(:release, tag: ref, project: project) }
let(:badge) { Gitlab::Ci::Badge::Release::LatestRelease.new(project, user) }
let(:template) { described_class.new(badge) }
before do
project.add_guest(user)
end
describe '#key_text' do
it 'defaults to latest release' do
expect(template.key_text).to eq 'Latest Release'
end
it 'returns custom key text' do
key_text = 'Test Release'
badge = Gitlab::Ci::Badge::Release::LatestRelease.new(project, user, opts: { key_text: key_text })
expect(described_class.new(badge).key_text).to eq key_text
end
end
describe '#value_text' do
context 'when a release exists' do
it 'returns the tag of the release' do
expect(template.value_text).to eq ref
end
end
context 'no releases exist' do
before do
allow(badge).to receive(:tag).and_return(nil)
end
it 'returns string that latest release is none' do
expect(template.value_text).to eq 'none'
end
end
end
describe '#key_width' do
it 'returns the default key width' do
expect(template.key_width).to eq 90
end
it 'returns custom key width' do
key_width = 100
badge = Gitlab::Ci::Badge::Release::LatestRelease.new(project, user, opts: { key_width: key_width })
expect(described_class.new(badge).key_width).to eq key_width
end
end
describe '#value_width' do
it 'has a fixed value width' do
expect(template.value_width).to eq 54
end
end
describe '#key_color' do
it 'always has the same color' do
expect(template.key_color).to eq '#555'
end
end
describe '#value_color' do
context 'when release exists' do
it 'is blue' do
expect(template.value_color).to eq '#3076af'
end
end
context 'when release does not exist' do
before do
allow(badge).to receive(:tag).and_return(nil)
end
it 'is red' do
expect(template.value_color).to eq '#e05d44'
end
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