diff options
author | Robert Speicher <robert@gitlab.com> | 2018-08-15 16:47:34 +0000 |
---|---|---|
committer | Robert Speicher <robert@gitlab.com> | 2018-08-15 16:47:34 +0000 |
commit | 085ed2862ce5c53362505bfc759ac8b1b881766d (patch) | |
tree | c2427414cb24ee4c0cca9bfe5d99d9df06b1e93e | |
parent | 39e8f0f2db57ed483db394bc38f67692d553d152 (diff) | |
parent | d7be0dc818702b19d44afec575a3423c0d0fed25 (diff) | |
download | gitlab-ce-085ed2862ce5c53362505bfc759ac8b1b881766d.tar.gz |
Merge branch 'backport-5986-license-templates' into 'master'
Core backports from the Premium license templates feature
See merge request gitlab-org/gitlab-ce!21212
-rw-r--r-- | app/assets/javascripts/pages/admin/application_settings/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/project_select.js | 8 | ||||
-rw-r--r-- | app/finders/license_template_finder.rb | 36 | ||||
-rw-r--r-- | app/helpers/blob_helper.rb | 12 | ||||
-rw-r--r-- | app/models/license_template.rb | 53 | ||||
-rw-r--r-- | app/views/admin/application_settings/show.html.haml | 2 | ||||
-rw-r--r-- | lib/api/entities.rb | 2 | ||||
-rw-r--r-- | lib/api/templates.rb | 44 | ||||
-rw-r--r-- | lib/gitlab/template/finders/base_template_finder.rb | 2 | ||||
-rw-r--r-- | lib/gitlab/template/finders/repo_template_finder.rb | 10 | ||||
-rw-r--r-- | spec/finders/license_template_finder_spec.rb | 49 | ||||
-rw-r--r-- | spec/lib/gitlab/template/finders/repo_template_finders_spec.rb | 37 | ||||
-rw-r--r-- | spec/models/license_template_spec.rb | 59 | ||||
-rw-r--r-- | spec/requests/api/templates_spec.rb | 3 |
14 files changed, 277 insertions, 42 deletions
diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js index 48d75f5443b..47bd70537f1 100644 --- a/app/assets/javascripts/pages/admin/application_settings/index.js +++ b/app/assets/javascripts/pages/admin/application_settings/index.js @@ -1,6 +1,8 @@ import initSettingsPanels from '~/settings_panels'; +import projectSelect from '~/project_select'; document.addEventListener('DOMContentLoaded', () => { // Initialize expandable settings panels initSettingsPanels(); + projectSelect(); }); diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index bce7556bd40..6f3b32f8eea 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -14,6 +14,7 @@ export default function projectSelect() { this.orderBy = $(select).data('orderBy') || 'id'; this.withIssuesEnabled = $(select).data('withIssuesEnabled'); this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled'); + this.allowClear = $(select).data('allowClear') || false; placeholder = "Search for project"; if (this.includeGroups) { @@ -71,6 +72,13 @@ export default function projectSelect() { text: function (project) { 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" }); if (simpleFilter) return select; diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb new file mode 100644 index 00000000000..fad33f0eca2 --- /dev/null +++ b/app/finders/license_template_finder.rb @@ -0,0 +1,36 @@ +# 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 diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 7eb45ddd117..b61cbd5418a 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -182,12 +182,14 @@ module BlobHelper def 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 = { - Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } }, - Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } } - } + @licenses_for_select = categories.each_with_object({}) do |category, hash| + hash[category] = grouped_licenses[category].map do |license| + { name: license.name, id: license.id } + end + end end def ref_project diff --git a/app/models/license_template.rb b/app/models/license_template.rb new file mode 100644 index 00000000000..0ad75b27827 --- /dev/null +++ b/app/models/license_template.rb @@ -0,0 +1,53 @@ +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 diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml index 258d50ad676..6133a7646f4 100644 --- a/app/views/admin/application_settings/show.html.haml +++ b/app/views/admin/application_settings/show.html.haml @@ -325,6 +325,8 @@ .settings-content = 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) } .settings-header %h4 diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 458ee320099..b6393fdef19 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1159,7 +1159,7 @@ module API class License < Grape::Entity expose :key, :name, :nickname - expose :featured, as: :popular + expose :popular?, as: :popular expose :url, as: :html_url expose(:source_url) { |license| license.meta['source'] } expose(:description) { |license| license.meta['description'] } diff --git a/lib/api/templates.rb b/lib/api/templates.rb index 41862768a3f..927baaea652 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -16,31 +16,8 @@ module API gitlab_version: 8.15 } }.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 - 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) not_found!(template_type.to_s.singularize) unless template present template, with: Entities::Template @@ -56,11 +33,12 @@ module API use :pagination end get "templates/licenses" do - options = { - featured: declared(params)[:popular].present? ? true : nil - } - licences = ::Kaminari.paginate_array(Licensee::License.all(options)) - present paginate(licences), with: Entities::License + popular = declared(params)[:popular] + popular = to_boolean(popular) if popular.present? + + templates = LicenseTemplateFinder.new(popular: popular).execute + + present paginate(::Kaminari.paginate_array(templates)), with: ::API::Entities::License end desc 'Get the text for a specific license' do @@ -71,9 +49,15 @@ module API requires :name, type: String, desc: 'The name of the template' end 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 end diff --git a/lib/gitlab/template/finders/base_template_finder.rb b/lib/gitlab/template/finders/base_template_finder.rb index 473b05257c6..a5105439b12 100644 --- a/lib/gitlab/template/finders/base_template_finder.rb +++ b/lib/gitlab/template/finders/base_template_finder.rb @@ -21,7 +21,7 @@ module Gitlab def category_directory(category) return @base_dir unless category.present? - @base_dir + @categories[category] + File.join(@base_dir, @categories[category]) end class << self diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb index 33f07fa0120..29bc2393ff9 100644 --- a/lib/gitlab/template/finders/repo_template_finder.rb +++ b/lib/gitlab/template/finders/repo_template_finder.rb @@ -27,7 +27,7 @@ module Gitlab directory = select_directory(file_name) raise FileNotFoundError if directory.nil? - category_directory(directory) + file_name + File.join(category_directory(directory), file_name) end def list_files_for(dir) @@ -37,8 +37,8 @@ module Gitlab entries = @repository.tree(:head, dir).entries - names = entries.map(&:name) - names.select { |f| f =~ self.class.filter_regex(@extension) } + paths = entries.map(&:path) + paths.select { |f| f =~ self.class.filter_regex(@extension) } end private @@ -47,10 +47,10 @@ module Gitlab return [] unless @commit # Insert root as directory - directories = ["", @categories.keys] + directories = ["", *@categories.keys] directories.find do |category| - path = category_directory(category) + file_name + path = File.join(category_directory(category), file_name) @repository.blob_at(@commit.id, path) end end diff --git a/spec/finders/license_template_finder_spec.rb b/spec/finders/license_template_finder_spec.rb new file mode 100644 index 00000000000..a97903103c9 --- /dev/null +++ b/spec/finders/license_template_finder_spec.rb @@ -0,0 +1,49 @@ +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 diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb new file mode 100644 index 00000000000..2eabccd5dff --- /dev/null +++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb @@ -0,0 +1,37 @@ +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 diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb new file mode 100644 index 00000000000..c633e1908d4 --- /dev/null +++ b/spec/models/license_template_spec.rb @@ -0,0 +1,59 @@ +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 diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index 6bb53fdc98d..d1e16ab9ca9 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -56,6 +56,8 @@ describe API::Templates do end it 'returns a license template' do + expect(response).to have_gitlab_http_status(200) + expect(json_response['key']).to eq('mit') expect(json_response['name']).to eq('MIT License') expect(json_response['nickname']).to be_nil @@ -181,6 +183,7 @@ describe API::Templates do it 'replaces the copyright owner placeholder with the name of the current user' do 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}") end end |