Commit 085ed286 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'backport-5986-license-templates' into 'master'

Core backports from the Premium license templates feature

See merge request gitlab-org/gitlab-ce!21212
parents 39e8f0f2 d7be0dc8
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
projectSelect();
}); });
...@@ -14,6 +14,7 @@ export default function projectSelect() { ...@@ -14,6 +14,7 @@ export default function projectSelect() {
this.orderBy = $(select).data('orderBy') || 'id'; this.orderBy = $(select).data('orderBy') || 'id';
this.withIssuesEnabled = $(select).data('withIssuesEnabled'); this.withIssuesEnabled = $(select).data('withIssuesEnabled');
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled'); this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
this.allowClear = $(select).data('allowClear') || false;
placeholder = "Search for project"; placeholder = "Search for project";
if (this.includeGroups) { if (this.includeGroups) {
...@@ -71,6 +72,13 @@ export default function projectSelect() { ...@@ -71,6 +72,13 @@ export default function projectSelect() {
text: function (project) { text: function (project) {
return project.name_with_namespace || project.name; return project.name_with_namespace || project.name;
}, },
initSelection: function(el, callback) {
return Api.project(el.val()).then(({ data }) => callback(data));
},
allowClear: this.allowClear,
dropdownCssClass: "ajax-project-dropdown" dropdownCssClass: "ajax-project-dropdown"
}); });
if (simpleFilter) return select; if (simpleFilter) return select;
......
# LicenseTemplateFinder
#
# Used to find license templates, which may come from a variety of external
# sources
#
# Arguments:
# popular: boolean. When set to true, only "popular" licenses are shown. When
# false, all licenses except popular ones are shown. When nil (the
# default), *all* licenses will be shown.
class LicenseTemplateFinder
attr_reader :params
def initialize(params = {})
@params = params
end
def execute
Licensee::License.all(featured: popular_only?).map do |license|
LicenseTemplate.new(
id: license.key,
name: license.name,
nickname: license.nickname,
category: (license.featured? ? :Popular : :Other),
content: license.content,
url: license.url,
meta: license.meta
)
end
end
private
def popular_only?
params.fetch(:popular, nil)
end
end
...@@ -182,12 +182,14 @@ module BlobHelper ...@@ -182,12 +182,14 @@ module BlobHelper
def licenses_for_select def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select) return @licenses_for_select if defined?(@licenses_for_select)
licenses = Licensee::License.all grouped_licenses = LicenseTemplateFinder.new.execute.group_by(&:category)
categories = grouped_licenses.keys
@licenses_for_select = { @licenses_for_select = categories.each_with_object({}) do |category, hash|
Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } }, hash[category] = grouped_licenses[category].map do |license|
Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } } { name: license.name, id: license.id }
} end
end
end end
def ref_project def ref_project
......
class LicenseTemplate
PROJECT_TEMPLATE_REGEX =
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
[\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]}xi.freeze
attr_reader :id, :name, :category, :nickname, :url, :meta
alias_method :key, :id
def initialize(id:, name:, category:, content:, nickname: nil, url: nil, meta: {})
@id = id
@name = name
@category = category
@content = content
@nickname = nickname
@url = url
@meta = meta
end
def popular?
category == :Popular
end
alias_method :featured?, :popular?
# Returns the text of the license
def content
if @content.respond_to?(:call)
@content = @content.call
else
@content
end
end
# Populate placeholders in the LicenseTemplate content
def resolve!(project_name: nil, fullname: nil, year: Time.now.year.to_s)
# Ensure the string isn't shared with any other instance of LicenseTemplate
new_content = content.dup
new_content.gsub!(YEAR_TEMPLATE_REGEX, year) if year.present?
new_content.gsub!(PROJECT_TEMPLATE_REGEX, project_name) if project_name.present?
new_content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname.present?
@content = new_content
self
end
end
...@@ -325,6 +325,8 @@ ...@@ -325,6 +325,8 @@
.settings-content .settings-content
= render partial: 'repository_mirrors_form' = render partial: 'repository_mirrors_form'
= render_if_exists 'admin/application_settings/templates', expanded: expanded
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) } %section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
%h4 %h4
......
...@@ -1159,7 +1159,7 @@ module API ...@@ -1159,7 +1159,7 @@ module API
class License < Grape::Entity class License < Grape::Entity
expose :key, :name, :nickname expose :key, :name, :nickname
expose :featured, as: :popular expose :popular?, as: :popular
expose :url, as: :html_url expose :url, as: :html_url
expose(:source_url) { |license| license.meta['source'] } expose(:source_url) { |license| license.meta['source'] }
expose(:description) { |license| license.meta['description'] } expose(:description) { |license| license.meta['description'] }
......
...@@ -16,31 +16,8 @@ module API ...@@ -16,31 +16,8 @@ module API
gitlab_version: 8.15 gitlab_version: 8.15
} }
}.freeze }.freeze
PROJECT_TEMPLATE_REGEX =
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
[\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]}xi.freeze
helpers do helpers do
def parsed_license_template
# We create a fresh Licensee::License object since we'll modify its
# content in place below.
template = Licensee::License.new(params[:name])
template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
fullname = params[:fullname].presence || current_user.try(:name)
template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
template
end
def render_response(template_type, template) def render_response(template_type, template)
not_found!(template_type.to_s.singularize) unless template not_found!(template_type.to_s.singularize) unless template
present template, with: Entities::Template present template, with: Entities::Template
...@@ -56,11 +33,12 @@ module API ...@@ -56,11 +33,12 @@ module API
use :pagination use :pagination
end end
get "templates/licenses" do get "templates/licenses" do
options = { popular = declared(params)[:popular]
featured: declared(params)[:popular].present? ? true : nil popular = to_boolean(popular) if popular.present?
}
licences = ::Kaminari.paginate_array(Licensee::License.all(options)) templates = LicenseTemplateFinder.new(popular: popular).execute
present paginate(licences), with: Entities::License
present paginate(::Kaminari.paginate_array(templates)), with: ::API::Entities::License
end end
desc 'Get the text for a specific license' do desc 'Get the text for a specific license' do
...@@ -71,9 +49,15 @@ module API ...@@ -71,9 +49,15 @@ module API
requires :name, type: String, desc: 'The name of the template' requires :name, type: String, desc: 'The name of the template'
end end
get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do
not_found!('License') unless Licensee::License.find(declared(params)[:name]) templates = LicenseTemplateFinder.new.execute
template = templates.find { |template| template.key == params[:name] }
not_found!('License') unless template.present?
template = parsed_license_template template.resolve!(
project_name: params[:project].presence,
fullname: params[:fullname].presence || current_user&.name
)
present template, with: ::API::Entities::License present template, with: ::API::Entities::License
end end
......
...@@ -21,7 +21,7 @@ module Gitlab ...@@ -21,7 +21,7 @@ module Gitlab
def category_directory(category) def category_directory(category)
return @base_dir unless category.present? return @base_dir unless category.present?
@base_dir + @categories[category] File.join(@base_dir, @categories[category])
end end
class << self class << self
......
...@@ -27,7 +27,7 @@ module Gitlab ...@@ -27,7 +27,7 @@ module Gitlab
directory = select_directory(file_name) directory = select_directory(file_name)
raise FileNotFoundError if directory.nil? raise FileNotFoundError if directory.nil?
category_directory(directory) + file_name File.join(category_directory(directory), file_name)
end end
def list_files_for(dir) def list_files_for(dir)
...@@ -37,8 +37,8 @@ module Gitlab ...@@ -37,8 +37,8 @@ module Gitlab
entries = @repository.tree(:head, dir).entries entries = @repository.tree(:head, dir).entries
names = entries.map(&:name) paths = entries.map(&:path)
names.select { |f| f =~ self.class.filter_regex(@extension) } paths.select { |f| f =~ self.class.filter_regex(@extension) }
end end
private private
...@@ -47,10 +47,10 @@ module Gitlab ...@@ -47,10 +47,10 @@ module Gitlab
return [] unless @commit return [] unless @commit
# Insert root as directory # Insert root as directory
directories = ["", @categories.keys] directories = ["", *@categories.keys]
directories.find do |category| directories.find do |category|
path = category_directory(category) + file_name path = File.join(category_directory(category), file_name)
@repository.blob_at(@commit.id, path) @repository.blob_at(@commit.id, path)
end end
end end
......
require 'spec_helper'
describe LicenseTemplateFinder do
describe '#execute' do
subject(:result) { described_class.new(params).execute }
let(:categories) { categorised_licenses.keys }
let(:categorised_licenses) { result.group_by(&:category) }
context 'popular: true' do
let(:params) { { popular: true } }
it 'only returns popular licenses' do
expect(categories).to contain_exactly(:Popular)
expect(categorised_licenses[:Popular]).to be_present
end
end
context 'popular: false' do
let(:params) { { popular: false } }
it 'only returns unpopular licenses' do
expect(categories).to contain_exactly(:Other)
expect(categorised_licenses[:Other]).to be_present
end
end
context 'popular: nil' do
let(:params) { { popular: nil } }
it 'returns all licenses known by the Licensee gem' do
from_licensee = Licensee::License.all.map { |l| l.key }
expect(result.map(&:id)).to match_array(from_licensee)
end
it 'correctly copies all attributes' do
licensee = Licensee::License.all.first
found = result.find { |r| r.key == licensee.key }
aggregate_failures do
%i[key name content nickname url meta featured?].each do |k|
expect(found.public_send(k)).to eq(licensee.public_send(k))
end
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Template::Finders::RepoTemplateFinder do
set(:project) { create(:project, :repository) }
let(:categories) { { 'HTML' => 'html' } }
subject(:finder) { described_class.new(project, 'files/', '.html', categories) }
describe '#read' do
it 'returns the content of the given path' do
result = finder.read('files/html/500.html')
expect(result).to be_present
end
it 'raises an error if the path does not exist' do
expect { finder.read('does/not/exist') }.to raise_error(described_class::FileNotFoundError)
end
end
describe '#find' do
it 'returns the full path of the found template' do
result = finder.find('500')
expect(result).to eq('files/html/500.html')
end
end
describe '#list_files_for' do
it 'returns the full path of the found files' do
result = finder.list_files_for('files/html')
expect(result).to contain_exactly('files/html/500.html')
end
end
end
require 'spec_helper'
describe LicenseTemplate do
describe '#content' do
it 'calls a proc exactly once if provided' do
lazy = build_template(-> { 'bar' })
content = lazy.content
expect(content).to eq('bar')
expect(content.object_id).to eq(lazy.content.object_id)
content.replace('foo')
expect(lazy.content).to eq('foo')
end
it 'returns a string if provided' do
lazy = build_template('bar')
expect(lazy.content).to eq('bar')
end
end
describe '#resolve!' do
let(:content) do
<<~TEXT
Pretend License
[project]
Copyright (c) [year] [fullname]
TEXT
end
let(:expected) do
<<~TEXT
Pretend License
Foo Project
Copyright (c) 1985 Nick Thomas
TEXT
end
let(:template) { build_template(content) }
it 'updates placeholders in a copy of the template content' do
expect(template.content.object_id).to eq(content.object_id)
template.resolve!(project_name: "Foo Project", fullname: "Nick Thomas", year: "1985")
expect(template.content).to eq(expected)
expect(template.content.object_id).not_to eq(content.object_id)
end
end
def build_template(content)
described_class.new(id: 'foo', name: 'foo', category: :Other, content: content)
end
end
...@@ -56,6 +56,8 @@ describe API::Templates do ...@@ -56,6 +56,8 @@ describe API::Templates do
end end
it 'returns a license template' do it 'returns a license template' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['key']).to eq('mit') expect(json_response['key']).to eq('mit')
expect(json_response['name']).to eq('MIT License') expect(json_response['name']).to eq('MIT License')
expect(json_response['nickname']).to be_nil expect(json_response['nickname']).to be_nil
...@@ -181,6 +183,7 @@ describe API::Templates do ...@@ -181,6 +183,7 @@ describe API::Templates do
it 'replaces the copyright owner placeholder with the name of the current user' do it 'replaces the copyright owner placeholder with the name of the current user' do
get api('/templates/licenses/mit', user) get api('/templates/licenses/mit', user)
expect(response).to have_gitlab_http_status(200)
expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}")
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