From c0dfaf98ac60fc928a2df5f7f355b262f0fcaf91 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 14:47:03 +0530 Subject: A working implementation of a project reference filter which links project references to project profile. --- app/models/project.rb | 18 +++ lib/banzai/filter/project_reference_filter.rb | 114 ++++++++++++++++ lib/banzai/pipeline/gfm_pipeline.rb | 1 + lib/banzai/reference_parser/project_parser.rb | 17 +++ .../banzai/filter/project_reference_filter_spec.rb | 149 +++++++++++++++++++++ .../banzai/reference_parser/project_parser_spec.rb | 30 +++++ spec/models/project_spec.rb | 9 ++ 7 files changed, 338 insertions(+) create mode 100644 lib/banzai/filter/project_reference_filter.rb create mode 100644 lib/banzai/reference_parser/project_parser.rb create mode 100644 spec/lib/banzai/filter/project_reference_filter_spec.rb create mode 100644 spec/lib/banzai/reference_parser/project_parser_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index d91d7dcfe9a..7a8e3f8a7ec 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -456,6 +456,20 @@ class Project < ActiveRecord::Base }x end + def reference_postfix + '>' + end + + # Pattern used to extract `project>` project references from text + # (?!\w) matches any non-word character + def markdown_reference_pattern + %r{ + #{reference_pattern} + (#{reference_postfix}|#{CGI.escapeHTML(reference_postfix)}) + (?!\w) + }x + end + def trending joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id') .reorder('trending_projects.id ASC') @@ -884,6 +898,10 @@ class Project < ActiveRecord::Base end end + def to_reference_with_postfix + "#{to_reference(full: true)}#{self.class.reference_postfix}" + end + # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) if full || cross_namespace_reference?(from) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb new file mode 100644 index 00000000000..ed69f7eae92 --- /dev/null +++ b/lib/banzai/filter/project_reference_filter.rb @@ -0,0 +1,114 @@ +module Banzai + module Filter + # HTML filter that replaces project references with links. + class ProjectReferenceFilter < ReferenceFilter + self.reference_type = :project + + # Public: Find `project>` project references in text + # + # ProjectReferenceFilter.references_in(text) do |match, project| + # "#{project}>" + # end + # + # text - String text to search. + # + # Yields the String match, and the String project name. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(Project.markdown_reference_pattern) do |match| + yield match, "#{$~[:namespace]}/#{$~[:project]}" + end + end + + def call + ref_pattern = Project.markdown_reference_pattern + ref_pattern_start = /\A#{ref_pattern}\z/ + + nodes.each do |node| + if text_node?(node) + replace_text_when_pattern_matches(node, ref_pattern) do |content| + project_link_filter(content) + end + elsif element_node?(node) + yield_valid_link(node) do |link, inner_html| + if link =~ ref_pattern_start + replace_link_node_with_href(node, link) do + project_link_filter(link, link_content: inner_html) + end + end + end + end + end + + doc + end + + # Replace `project>` project references in text with links to the referenced + # project page. + # + # text - String text to replace references in. + # link_content - Original content of the link being replaced. + # + # Returns a String with `project>` references replaced with links. All links + # have `gfm` and `gfm-project` class names attached for styling. + def project_link_filter(text, link_content: nil) + self.class.references_in(text) do |match, project_name| + cached_call(:banzai_url_for_object, match, path: [Project, project_name.downcase]) do + if project = projects_hash[project_name.downcase] + link_to_project(project, link_content: link_content) || match + else + match + end + end + end + end + + # Returns a Hash containing all Namespace objects for the project + # references in the current document. + # + # The keys of this Hash are the namespace paths, the values the + # corresponding Namespace objects. + def projects_hash + @projects ||= Project.where_full_path_in(projects) + .index_by(&:full_path) + .transform_keys(&:downcase) + end + + # Returns all projects referenced in the current document. + def projects + refs = Set.new + + nodes.each do |node| + node.to_html.scan(Project.markdown_reference_pattern) do + refs << "#{$~[:namespace]}/#{$~[:project]}" + end + end + + refs.to_a + end + + private + + def urls + Gitlab::Routing.url_helpers + end + + def link_class + reference_class(:project) + end + + def link_to_project(project, link_content: nil) + url = urls.project_url(project, only_path: context[:only_path]) + data = data_attribute(project: project.id) + content = link_content || project.full_path + Project.reference_postfix + + link_tag(url, data, content, project.name) + end + + def link_tag(url, data, link_content, title) + %(#{link_content}) + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 0d9b874ef85..9295ca9efcc 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -25,6 +25,7 @@ module Banzai Filter::ExternalLinkFilter, Filter::UserReferenceFilter, + Filter::ProjectReferenceFilter, Filter::IssueReferenceFilter, Filter::ExternalIssueReferenceFilter, Filter::MergeRequestReferenceFilter, diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb new file mode 100644 index 00000000000..54fd3c38a85 --- /dev/null +++ b/lib/banzai/reference_parser/project_parser.rb @@ -0,0 +1,17 @@ +module Banzai + module ReferenceParser + class ProjectParser < BaseParser + self.reference_type = :project + + def references_relation + Project + end + + private + + def can_read_reference?(user, ref_project, node) + can?(user, :read_project, ref_project) + end + end + end +end diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb new file mode 100644 index 00000000000..2ace8496b08 --- /dev/null +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -0,0 +1,149 @@ +require 'spec_helper' + +describe Banzai::Filter::ProjectReferenceFilter do + include FilterSpecHelper + + def invalidate_reference(reference) + "#{reference.reverse}" + end + + def get_reference(project) + project.to_reference_with_postfix + end + + let(:project) { create(:project, :public) } + let(:reference) { get_reference(project) } + + it 'ignores invalid projects' do + exp = act = "Hey #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp)) + end + + it 'ignores references with text after the > sign' do + exp = act = "Hey #{reference}foo" + expect(reference_filter(act).to_html).to eq CGI.escapeHTML(exp) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Hey #{CGI.escapeHTML(reference)}" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'mentioning a project' do + it_behaves_like 'a reference containing an element node' + + it 'links to a Project' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) + end + + it 'links to a Project with a period' do + project = create(:project, name: 'alphA.Beta') + + doc = reference_filter("Hey #{get_reference(project)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a Project with an underscore' do + project = create(:project, name: 'ping_pong_king') + + doc = reference_filter("Hey #{get_reference(project)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a Project with different case-sensitivity' do + project = create(:project, name: 'RescueRanger') + reference = get_reference(project) + + doc = reference_filter("Hey #{reference.upcase}") + expect(doc.css('a').length).to eq 1 + expect(doc.css('a').text).to eq(reference) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.namespace.owner_id.to_s + end + end + + it 'includes default classes' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project has-tooltip' + end + + it 'supports an :only_path context' do + doc = reference_filter("Hey #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.project_path(project) + end + + context 'referencing a project in a link href' do + let(:reference) { %Q{Project} } + + it 'links to a Project' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(%r{\(Project\.\)}) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + end + + context 'in group context' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + + let(:nested_group) { create(:group, :nested) } + let(:nested_project) { create(:project, group: nested_group) } + + it 'supports mentioning a project' do + reference = get_reference(project) + doc = reference_filter("Hey #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) + end + + it 'supports mentioning a project in a nested group' do + reference = get_reference(nested_project) + doc = reference_filter("Hey #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.project_url(nested_project) + end + end + + describe '#projects_hash' do + it 'returns a Hash containing all Projects' do + document = Nokogiri::HTML.fragment("

#{get_reference(project)}

") + filter = described_class.new(document, project: project) + + expect(filter.projects_hash).to eq({ project.full_path => project }) + end + end + + describe '#projects' do + it 'returns the projects mentioned in a document' do + document = Nokogiri::HTML.fragment("

#{get_reference(project)}

") + filter = described_class.new(document, project: project) + + expect(filter.projects).to eq([project.full_path]) + end + end +end diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb new file mode 100644 index 00000000000..4a6c2294fbd --- /dev/null +++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Banzai::ReferenceParser::ProjectParser do + include ReferenceParserHelpers + + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } + + describe '#referenced_by' do + describe 'when the link has a data-project attribute' do + context 'using an existing project ID' do + it 'returns an Array of projects' do + link['data-project'] = project.id.to_s + + expect(subject.referenced_by([link])).to eq([project]) + end + end + + context 'using a non-existing project ID' do + it 'returns an empty Array' do + link['data-project'] = '' + + expect(subject.referenced_by([link])).to eq([]) + end + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index abdc65336ca..acc7821a21e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -344,6 +344,15 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end + describe '#to_reference_with_postfix' do + it 'returns the full path with reference_postfix' do + namespace = create(:namespace, path: 'sample-namespace') + project = create(:project, path: 'sample-project', namespace: namespace) + + expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' + end + end + describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } -- cgit v1.2.1 From a4577cb5f9403d4e777de9d252854a78f3579cbb Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 16:11:10 +0530 Subject: Correct the "includes a data-project attribute" test in project_reference_filter_spec --- spec/lib/banzai/filter/project_reference_filter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index 2ace8496b08..e5b18ee2cdb 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -68,7 +68,7 @@ describe Banzai::Filter::ProjectReferenceFilter do link = doc.css('a').first expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.namespace.owner_id.to_s + expect(link.attr('data-project')).to eq project.id.to_s end end -- cgit v1.2.1 From 3abe12707ee8c84aa7b4106db3092a554a31ab04 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 16:25:09 +0530 Subject: Add an entry about project reference linking in the markdown doc. --- doc/user/markdown.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 8e87c896a72..3906bed8682 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -259,6 +259,7 @@ GFM will recognize the following: | `@user_name` | specific user | | `@group_name` | specific group | | `@all` | entire team | +| `namespace/project>` | project | | `#12345` | issue | | `!123` | merge request | | `$123` | snippet | -- cgit v1.2.1 From dedbb67f101153fb72385c8077fda190d7f641fc Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 16:27:42 +0530 Subject: Add a comment explaining the need to check for the escaped form of '>' when searching for project references. --- app/models/project.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 7a8e3f8a7ec..fab496c02ba 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -462,6 +462,8 @@ class Project < ActiveRecord::Base # Pattern used to extract `project>` project references from text # (?!\w) matches any non-word character + # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped + # when the reference comes from an external source. def markdown_reference_pattern %r{ #{reference_pattern} -- cgit v1.2.1 From 28cf16a772d359cac3eb589553b579cdc2de6ca9 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 18:49:07 +0530 Subject: Correct the comment above the projects_hash function in the project_reference_filter --- lib/banzai/filter/project_reference_filter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index ed69f7eae92..4a162733438 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -64,11 +64,11 @@ module Banzai end end - # Returns a Hash containing all Namespace objects for the project + # Returns a Hash containing all Project objects for the project # references in the current document. # - # The keys of this Hash are the namespace paths, the values the - # corresponding Namespace objects. + # The keys of this Hash are the project paths, the values the + # corresponding Project objects. def projects_hash @projects ||= Project.where_full_path_in(projects) .index_by(&:full_path) -- cgit v1.2.1 From b74ed743e4e7bfb7570f3d3146ae2254a1fa8c9b Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 00:04:37 +0530 Subject: Correct the comment above the Project.markdown_reference_pattern method --- app/models/project.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index fab496c02ba..53608374d39 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -460,8 +460,8 @@ class Project < ActiveRecord::Base '>' end - # Pattern used to extract `project>` project references from text - # (?!\w) matches any non-word character + # Pattern used to extract `namespace/project>` project references from text. + # (?!\w) matches any non-word character. # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped # when the reference comes from an external source. def markdown_reference_pattern -- cgit v1.2.1 From 4fb466249fd06b87507244796286fbf56f65200a Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 00:16:25 +0530 Subject: Add a spec for a user viewing a reference to a private project --- .../banzai/reference_parser/project_parser_spec.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb index 4a6c2294fbd..c6a4d15e47c 100644 --- a/spec/lib/banzai/reference_parser/project_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb @@ -14,7 +14,7 @@ describe Banzai::ReferenceParser::ProjectParser do it 'returns an Array of projects' do link['data-project'] = project.id.to_s - expect(subject.referenced_by([link])).to eq([project]) + expect(subject.gather_references([link])).to eq([project]) end end @@ -22,7 +22,25 @@ describe Banzai::ReferenceParser::ProjectParser do it 'returns an empty Array' do link['data-project'] = '' - expect(subject.referenced_by([link])).to eq([]) + expect(subject.gather_references([link])).to eq([]) + end + end + + context 'using a private project ID' do + it 'returns an empty Array when unauthorized' do + private_project = create(:project, :private) + + link['data-project'] = private_project.id.to_s + + expect(subject.gather_references([link])).to eq([]) + end + + it 'returns an Array when authorized' do + private_project = create(:project, :private, namespace: user.namespace) + + link['data-project'] = private_project.id.to_s + + expect(subject.gather_references([link])).to eq([private_project]) end end end -- cgit v1.2.1 From e533826382b55705d1692d755c631ca08d4afd5d Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 00:36:34 +0530 Subject: Move common tests from user and project reference filter specs to a shared example --- .../banzai/filter/project_reference_filter_spec.rb | 74 +------------------ .../banzai/filter/user_reference_filter_spec.rb | 85 +++------------------- .../banzai/reference_filter_shared_examples.rb | 73 +++++++++++++++++++ 3 files changed, 89 insertions(+), 143 deletions(-) diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index e5b18ee2cdb..a57f4644866 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -12,8 +12,12 @@ describe Banzai::Filter::ProjectReferenceFilter do end let(:project) { create(:project, :public) } + subject { project } + let(:subject_name) { "project" } let(:reference) { get_reference(project) } + it_behaves_like 'user reference or project reference' + it 'ignores invalid projects' do exp = act = "Hey #{invalidate_reference(reference)}" @@ -32,81 +36,11 @@ describe Banzai::Filter::ProjectReferenceFilter do end end - context 'mentioning a project' do - it_behaves_like 'a reference containing an element node' - - it 'links to a Project' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) - end - - it 'links to a Project with a period' do - project = create(:project, name: 'alphA.Beta') - - doc = reference_filter("Hey #{get_reference(project)}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a Project with an underscore' do - project = create(:project, name: 'ping_pong_king') - - doc = reference_filter("Hey #{get_reference(project)}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a Project with different case-sensitivity' do - project = create(:project, name: 'RescueRanger') - reference = get_reference(project) - - doc = reference_filter("Hey #{reference.upcase}") - expect(doc.css('a').length).to eq 1 - expect(doc.css('a').text).to eq(reference) - end - - it 'includes a data-project attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - end - it 'includes default classes' do doc = reference_filter("Hey #{reference}") expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project has-tooltip' end - it 'supports an :only_path context' do - doc = reference_filter("Hey #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.project_path(project) - end - - context 'referencing a project in a link href' do - let(:reference) { %Q{Project} } - - it 'links to a Project' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) - end - - it 'links with adjacent text' do - doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(%r{\(Project\.\)}) - end - - it 'includes a data-project attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - end - context 'in group context' do let(:group) { create(:group) } let(:project) { create(:project, group: group) } diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 2f86a046d28..334d29a5368 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -3,9 +3,17 @@ require 'spec_helper' describe Banzai::Filter::UserReferenceFilter do include FilterSpecHelper + def get_reference(user) + user.to_reference + end + let(:project) { create(:project, :public) } let(:user) { create(:user) } - let(:reference) { user.to_reference } + subject { user } + let(:subject_name) { "user" } + let(:reference) { get_reference(user) } + + it_behaves_like 'user reference or project reference' it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -66,45 +74,6 @@ describe Banzai::Filter::UserReferenceFilter do end end - context 'mentioning a user' do - it_behaves_like 'a reference containing an element node' - - it 'links to a User' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links to a User with a period' do - user = create(:user, name: 'alphA.Beta') - - doc = reference_filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a User with an underscore' do - user = create(:user, name: 'ping_pong_king') - - doc = reference_filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a User with different case-sensitivity' do - user = create(:user, username: 'RescueRanger') - - doc = reference_filter("Hey #{user.to_reference.upcase}") - expect(doc.css('a').length).to eq 1 - expect(doc.css('a').text).to eq(user.to_reference) - end - - it 'includes a data-user attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-user') - expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s - end - end - context 'mentioning a group' do it_behaves_like 'a reference containing an element node' @@ -154,36 +123,6 @@ describe Banzai::Filter::UserReferenceFilter do expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip' end - it 'supports an :only_path context' do - doc = reference_filter("Hey #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.user_path(user) - end - - context 'referencing a user in a link href' do - let(:reference) { %Q{User} } - - it 'links to a User' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links with adjacent text' do - doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(%r{\(User\.\)}) - end - - it 'includes a data-user attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-user') - expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s - end - end - context 'when a project is not specified' do let(:project) { nil } @@ -227,7 +166,7 @@ describe Banzai::Filter::UserReferenceFilter do end it 'supports mentioning a single user' do - reference = group_member.to_reference + reference = get_reference(group_member) doc = reference_filter("Hey #{reference}", context) expect(doc.css('a').first.attr('href')).to eq urls.user_url(group_member) @@ -243,7 +182,7 @@ describe Banzai::Filter::UserReferenceFilter do describe '#namespaces' do it 'returns a Hash containing all Namespaces' do - document = Nokogiri::HTML.fragment("

#{user.to_reference}

") + document = Nokogiri::HTML.fragment("

#{get_reference(user)}

") filter = described_class.new(document, project: project) ns = user.namespace @@ -253,7 +192,7 @@ describe Banzai::Filter::UserReferenceFilter do describe '#usernames' do it 'returns the usernames mentioned in a document' do - document = Nokogiri::HTML.fragment("

#{user.to_reference}

") + document = Nokogiri::HTML.fragment("

#{get_reference(user)}

") filter = described_class.new(document, project: project) expect(filter.usernames).to eq([user.username]) diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb index eb5da662ab5..476d80f3a93 100644 --- a/spec/support/banzai/reference_filter_shared_examples.rb +++ b/spec/support/banzai/reference_filter_shared_examples.rb @@ -11,3 +11,76 @@ shared_examples 'a reference containing an element node' do expect(doc.children.first.inner_html).to eq(inner_html) end end + +# Requires a reference, subject and subject_name: +# subject { create(:user) } +# let(:reference) { subject.to_reference } +# let(:subject_name) { 'user' } +shared_examples 'user reference or project reference' do + shared_examples 'it contains a data- attribute' do + it 'includes a data- attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute("data-#{subject_name}") + expect(link.attr("data-#{subject_name}")).to eq subject.id.to_s + end + end + + context 'mentioning a resource' do + it_behaves_like 'a reference containing an element node' + it_behaves_like 'it contains a data- attribute' + + it "links to a resource" do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.send("#{subject_name}_url", subject) + end + + it 'links to a resource with a period' do + subject = create(subject_name.to_sym, name: 'alphA.Beta') + + doc = reference_filter("Hey #{get_reference(subject)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a resource with an underscore' do + subject = create(subject_name.to_sym, name: 'ping_pong_king') + + doc = reference_filter("Hey #{get_reference(subject)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a resource with different case-sensitivity' do + subject = create(subject_name.to_sym, name: 'RescueRanger') + reference = get_reference(subject) + + doc = reference_filter("Hey #{reference.upcase}") + expect(doc.css('a').length).to eq 1 + expect(doc.css('a').text).to eq(reference) + end + end + + it 'supports an :only_path context' do + doc = reference_filter("Hey #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.send "#{subject_name}_path", subject + end + + context 'referencing a resource in a link href' do + let(:reference) { %Q{Some text} } + + it_behaves_like 'it contains a data- attribute' + + it 'links to the resource' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.send "#{subject_name}_url", subject + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(%r{\(Some text\.\)}) + end + end +end -- cgit v1.2.1 From 513d6a8457748295ad65cadbf946f125d81f2a4a Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 01:07:31 +0530 Subject: Use literal '>' as escaped form of '>' --- app/models/project.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 53608374d39..afeb4625cca 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -460,6 +460,10 @@ class Project < ActiveRecord::Base '>' end + def reference_postfix_escaped + '>' + end + # Pattern used to extract `namespace/project>` project references from text. # (?!\w) matches any non-word character. # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped @@ -467,7 +471,7 @@ class Project < ActiveRecord::Base def markdown_reference_pattern %r{ #{reference_pattern} - (#{reference_postfix}|#{CGI.escapeHTML(reference_postfix)}) + (#{reference_postfix}|#{reference_postfix_escaped}) (?!\w) }x end -- cgit v1.2.1 From 07ac9ee2f0861135e6cd514e956e6eb7019e7096 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 01:09:39 +0530 Subject: Correct the misleading name of a variable in ProjectReferenceFilter.project_link_filter --- lib/banzai/filter/project_reference_filter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 4a162733438..8bc4ceb92cd 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -53,9 +53,9 @@ module Banzai # Returns a String with `project>` references replaced with links. All links # have `gfm` and `gfm-project` class names attached for styling. def project_link_filter(text, link_content: nil) - self.class.references_in(text) do |match, project_name| - cached_call(:banzai_url_for_object, match, path: [Project, project_name.downcase]) do - if project = projects_hash[project_name.downcase] + self.class.references_in(text) do |match, project_path| + cached_call(:banzai_url_for_object, match, path: [Project, project_path.downcase]) do + if project = projects_hash[project_path.downcase] link_to_project(project, link_content: link_content) || match else match -- cgit v1.2.1 From c8302b2f50efad5c159fb9e01598259c80887dac Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 10:15:56 +0530 Subject: Correct the comments above the ProjectReferenceFilter#project_link_filter function to make it clear that the full project path is required in a reference. --- lib/banzai/filter/project_reference_filter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 8bc4ceb92cd..6f257a2d70d 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -44,13 +44,13 @@ module Banzai doc end - # Replace `project>` project references in text with links to the referenced + # Replace `namespace/project>` project references in text with links to the referenced # project page. # # text - String text to replace references in. # link_content - Original content of the link being replaced. # - # Returns a String with `project>` references replaced with links. All links + # Returns a String with `namespace/project>` references replaced with links. All links # have `gfm` and `gfm-project` class names attached for styling. def project_link_filter(text, link_content: nil) self.class.references_in(text) do |match, project_path| -- cgit v1.2.1 From 972f12218e6954e183d9654775c4cbedcc97d2eb Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 10:18:49 +0530 Subject: Eager load the namespace and route of a project in project_reference_filter.rb --- lib/banzai/filter/project_reference_filter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 6f257a2d70d..6eead2ab1c3 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -70,7 +70,8 @@ module Banzai # The keys of this Hash are the project paths, the values the # corresponding Project objects. def projects_hash - @projects ||= Project.where_full_path_in(projects) + @projects ||= Project.eager_load(:namespace, :route) + .where_full_path_in(projects) .index_by(&:full_path) .transform_keys(&:downcase) end -- cgit v1.2.1 From 6572e045acbf43139dfecfe0ed1b289dfa12b6fa Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 10:19:53 +0530 Subject: Add a changelog entry for MR 20285 - Add the ability to reference projects in markdown --- changelogs/unreleased/28930-add-project-reference-filter.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/28930-add-project-reference-filter.yml diff --git a/changelogs/unreleased/28930-add-project-reference-filter.yml b/changelogs/unreleased/28930-add-project-reference-filter.yml new file mode 100644 index 00000000000..c7679c5fe76 --- /dev/null +++ b/changelogs/unreleased/28930-add-project-reference-filter.yml @@ -0,0 +1,5 @@ +--- +title: Add the ability to reference projects in comments and other markdown text. +merge_request: 20285 +author: Reuben Pereira +type: added -- cgit v1.2.1 From 7429eb3b48515d05d3840b6ba8ca68581eeb5ebe Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 21:26:28 +0530 Subject: Eager load a project's route and its namespace with route in project_reference_filter --- lib/banzai/filter/project_reference_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 6eead2ab1c3..9e680d25b50 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -70,7 +70,7 @@ module Banzai # The keys of this Hash are the project paths, the values the # corresponding Project objects. def projects_hash - @projects ||= Project.eager_load(:namespace, :route) + @projects ||= Project.eager_load(:route, namespace: [:route]) .where_full_path_in(projects) .index_by(&:full_path) .transform_keys(&:downcase) -- cgit v1.2.1 From 9e9137a6607e95bbe1e113da02fb95c2b19d66f9 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 21:46:13 +0530 Subject: Remove the Project#to_reference_with_postfix method since it is only used in the project_reference_filter_spec --- app/models/project.rb | 4 ---- spec/lib/banzai/filter/project_reference_filter_spec.rb | 2 +- spec/models/project_spec.rb | 9 --------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index afeb4625cca..79beec77843 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -904,10 +904,6 @@ class Project < ActiveRecord::Base end end - def to_reference_with_postfix - "#{to_reference(full: true)}#{self.class.reference_postfix}" - end - # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) if full || cross_namespace_reference?(from) diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index a57f4644866..559293d5ac3 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -8,7 +8,7 @@ describe Banzai::Filter::ProjectReferenceFilter do end def get_reference(project) - project.to_reference_with_postfix + "#{project.to_reference(full: true)}#{Project.reference_postfix}" end let(:project) { create(:project, :public) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index acc7821a21e..abdc65336ca 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -344,15 +344,6 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end - describe '#to_reference_with_postfix' do - it 'returns the full path with reference_postfix' do - namespace = create(:namespace, path: 'sample-namespace') - project = create(:project, path: 'sample-project', namespace: namespace) - - expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' - end - end - describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } -- cgit v1.2.1 From d6f4810ea154d2f2303591ac37d616c8249fca9d Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 22:24:59 +0530 Subject: Correct the comment above the ProjectReferenceFilter.references_in method. --- lib/banzai/filter/project_reference_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 9e680d25b50..9764cfa4ef5 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -4,7 +4,7 @@ module Banzai class ProjectReferenceFilter < ReferenceFilter self.reference_type = :project - # Public: Find `project>` project references in text + # Public: Find `namespace/project>` project references in text # # ProjectReferenceFilter.references_in(text) do |match, project| # "#{project}>" -- cgit v1.2.1 From 77c53f61262e01197c89f5c59f627c38df83be53 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Wed, 4 Jul 2018 21:41:46 +0530 Subject: Remove the restriction preventing project references with text adjacent to the > character --- app/models/project.rb | 2 -- spec/lib/banzai/filter/project_reference_filter_spec.rb | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 79beec77843..9dfa9a144fd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -465,14 +465,12 @@ class Project < ActiveRecord::Base end # Pattern used to extract `namespace/project>` project references from text. - # (?!\w) matches any non-word character. # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped # when the reference comes from an external source. def markdown_reference_pattern %r{ #{reference_pattern} (#{reference_postfix}|#{reference_postfix_escaped}) - (?!\w) }x end diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index 559293d5ac3..20f809e010e 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -24,9 +24,9 @@ describe Banzai::Filter::ProjectReferenceFilter do expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp)) end - it 'ignores references with text after the > sign' do - exp = act = "Hey #{reference}foo" - expect(reference_filter(act).to_html).to eq CGI.escapeHTML(exp) + it 'allows references with text after the > character' do + doc = reference_filter("Hey #{reference}foo") + expect(doc.css('a').first.attr('href')).to eq urls.project_url(subject) end %w(pre code a style).each do |elem| -- cgit v1.2.1 From 2730ae1d869af4ddd48dc312d230c1bcafec19b5 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Thu, 5 Jul 2018 01:26:09 +0530 Subject: Use a custom ProjectParser#nodes_visible_to_user function so that the user permissions for all project references can be checked together --- lib/banzai/reference_parser/project_parser.rb | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index 54fd3c38a85..787cb671b2d 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -7,10 +7,29 @@ module Banzai Project end + def nodes_visible_to_user(user, nodes) + nodes_projects_hash = lazy { projects_for_nodes(nodes) } + project_attr = 'data-project' + + readable_project_ids = projects_readable_by_user(nodes_projects_hash.values, user) + + nodes.select do |node| + if node.has_attribute?(project_attr) + readable_project_ids.include?(nodes_projects_hash[node].try(:id)) + else + true + end + end + end + private - def can_read_reference?(user, ref_project, node) - can?(user, :read_project, ref_project) + # Returns an Array of Project ids that can be read by the given user. + # + # projects - The projects to reduce down to those readable by the user. + # user - The User for which to check the projects + def projects_readable_by_user(projects, user) + Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.collect(&:id)).pluck(:id) end end end -- cgit v1.2.1 From 74c81cc50e7d4deff11e9777b9522f8adf8b3c96 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Thu, 5 Jul 2018 01:55:55 +0530 Subject: Add back the Project#to_reference_with_postfix function since it can be used in the ProjectReferenceFilter#link_to_project function --- app/models/project.rb | 4 ++++ lib/banzai/filter/project_reference_filter.rb | 2 +- spec/lib/banzai/filter/project_reference_filter_spec.rb | 2 +- spec/models/project_spec.rb | 9 +++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 9dfa9a144fd..cf740de9405 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -902,6 +902,10 @@ class Project < ActiveRecord::Base end end + def to_reference_with_postfix + "#{to_reference(full: true)}#{self.class.reference_postfix}" + end + # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) if full || cross_namespace_reference?(from) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 9764cfa4ef5..fd2a86a6d45 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -102,7 +102,7 @@ module Banzai def link_to_project(project, link_content: nil) url = urls.project_url(project, only_path: context[:only_path]) data = data_attribute(project: project.id) - content = link_content || project.full_path + Project.reference_postfix + content = link_content || project.to_reference_with_postfix link_tag(url, data, content, project.name) end diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index 20f809e010e..13e1fc2d3e2 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -8,7 +8,7 @@ describe Banzai::Filter::ProjectReferenceFilter do end def get_reference(project) - "#{project.to_reference(full: true)}#{Project.reference_postfix}" + project.to_reference_with_postfix end let(:project) { create(:project, :public) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index abdc65336ca..acc7821a21e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -344,6 +344,15 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end + describe '#to_reference_with_postfix' do + it 'returns the full path with reference_postfix' do + namespace = create(:namespace, path: 'sample-namespace') + project = create(:project, path: 'sample-project', namespace: namespace) + + expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' + end + end + describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } -- cgit v1.2.1 From a08b5144fe64cf7afde116334c91e95f54c9f4fb Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Thu, 5 Jul 2018 21:13:46 +0530 Subject: Use map instead of collect in the ProjectParser#projects_readable_by_user function --- lib/banzai/reference_parser/project_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index 787cb671b2d..2bf02e3df53 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -29,7 +29,7 @@ module Banzai # projects - The projects to reduce down to those readable by the user. # user - The User for which to check the projects def projects_readable_by_user(projects, user) - Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.collect(&:id)).pluck(:id) + Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) end end end -- cgit v1.2.1 From 915709a4ab352cca1a06d213c865219b304d5cdc Mon Sep 17 00:00:00 2001 From: Landon Abney Date: Fri, 6 Jul 2018 20:32:37 +0000 Subject: Remove duplicate nip.io link The nip.io service was being linked to twice in the note on cert requests. --- doc/install/kubernetes/gitlab_omnibus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md index 9aee6b9dc74..c2c8a7a92fd 100644 --- a/doc/install/kubernetes/gitlab_omnibus.md +++ b/doc/install/kubernetes/gitlab_omnibus.md @@ -120,7 +120,7 @@ gitlabConfigStorageSize: 1Gi Ingress routing and SSL are automatically configured within this Chart. An NGINX ingress is provisioned and configured, and will route traffic to any service. SSL certificates are automatically created and configured by [kube-lego](https://github.com/kubernetes/charts/tree/master/stable/kube-lego). > **Note:** -Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [nip.io](http://nip.io) and [nip.io](http://nip.io) are unlikely to work. +Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [nip.io](http://nip.io) are unlikely to work. ## Installing GitLab using the Helm Chart > **Note:** -- cgit v1.2.1 From 8cc646fa0ec05820c83a163df74605f04c8877ea Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sun, 15 Jul 2018 17:25:04 +0530 Subject: Remove nodes_visible_to_user from ProjectParser and memoize readable_project_ids --- lib/banzai/reference_parser/project_parser.rb | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index 2bf02e3df53..a329af42681 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -1,35 +1,28 @@ module Banzai module ReferenceParser class ProjectParser < BaseParser + include Gitlab::Utils::StrongMemoize + self.reference_type = :project def references_relation Project end - def nodes_visible_to_user(user, nodes) - nodes_projects_hash = lazy { projects_for_nodes(nodes) } - project_attr = 'data-project' - - readable_project_ids = projects_readable_by_user(nodes_projects_hash.values, user) - - nodes.select do |node| - if node.has_attribute?(project_attr) - readable_project_ids.include?(nodes_projects_hash[node].try(:id)) - else - true - end - end - end - private # Returns an Array of Project ids that can be read by the given user. # # projects - The projects to reduce down to those readable by the user. # user - The User for which to check the projects - def projects_readable_by_user(projects, user) - Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) + def readable_project_ids_for(projects, user) + strong_memoize(:readable_project_ids_for) do + Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) + end + end + + def can_read_reference?(user, ref_project, node) + readable_project_ids_for(@projects_for_nodes.values, user).include?(ref_project.try(:id)) end end end -- cgit v1.2.1 From cfefab3259e41669d4326f48f79a9d168f94249f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 25 Jul 2018 12:05:40 +0100 Subject: port --- .../projects/settings/repository/show/index.js | 8 ++- .../projects/settings/repository/show/push_pull.js | 69 +++++++++++++++++++++ app/assets/stylesheets/pages/settings.scss | 10 ++++ app/helpers/mirror_helper.rb | 5 ++ app/views/projects/mirrors/_instructions.html.haml | 15 ++--- app/views/projects/mirrors/_push.html.haml | 50 ---------------- app/views/projects/mirrors/_push_pull.html.haml | 70 ++++++++++++++++++++++ .../projects/mirrors/_push_pull_form.html.haml | 6 ++ app/views/projects/mirrors/_show.html.haml | 4 +- .../shared/_remote_mirror_update_button.html.haml | 19 ++---- db/schema.rb | 9 +-- 11 files changed, 187 insertions(+), 78 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js create mode 100644 app/helpers/mirror_helper.rb delete mode 100644 app/views/projects/mirrors/_push.html.haml create mode 100644 app/views/projects/mirrors/_push_pull.html.haml create mode 100644 app/views/projects/mirrors/_push_pull_form.html.haml diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js index ffc84dc106b..923cd9efe87 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -1,3 +1,9 @@ import initForm from '../form'; +import PushPull from './push_pull'; -document.addEventListener('DOMContentLoaded', initForm); +document.addEventListener('DOMContentLoaded', () => { + initForm(); + + const pushPullContainer = document.querySelector('.js-mirror-settings'); + if (pushPullContainer) new PushPull(pushPullContainer).init(); +}); diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js b/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js new file mode 100644 index 00000000000..7ccba4a39b5 --- /dev/null +++ b/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js @@ -0,0 +1,69 @@ +import $ from 'jquery'; +import _ from 'underscore'; +import { __ } from '~/locale'; +import Flash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; + +export default class PushPull { + constructor(container) { + this.$container = $(container); + this.$form = $('.js-mirror-form', this.$container); + this.$urlInput = $('.js-mirror-url', this.$form); + this.$protectedBranchesInput = $('.js-mirror-protected', this.$form); + this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint'); + this.$table = $('.js-mirrors-table-body', this.$container); + } + + init() { + this.registerUpdateListeners(); + + this.$table.on('click', '.js-delete-mirror', this.deleteMirror.bind(this)); + } + + updateUrl() { + $('.js-mirror-url-hidden', this.$form).val(this.$urlInput.val()); + } + + updateProtectedBranches() { + const val = this.$protectedBranchesInput.get(0).checked + ? this.$protectedBranchesInput.val() + : '0'; + $('.js-mirror-protected-hidden', this.$form).val(val); + } + + registerUpdateListeners() { + this.$urlInput.on('change', () => this.updateUrl()); + this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches()); + } + + deleteMirror(event, existingPayload) { + const $target = $(event.currentTarget); + let payload = existingPayload; + + debugger; + + if (!payload) { + payload = { + project: { + remote_mirrors_attributes: { + id: $target.data('mirrorId'), + enabled: 0, + }, + }, + }; + } + + return axios + .put(this.mirrorEndpoint, payload) + .then(() => this.removeRow($target)) + .catch(() => Flash(__('Failed to remove mirror.'))); + } + + /* eslint-disable class-methods-use-this */ + removeRow($target) { + const row = $target.closest('tr'); + $('.js-delete-mirror', row).tooltip('hide'); + row.remove(); + } + /* eslint-enable class-methods-use-this */ +} diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 839ac5ba59b..03243581abf 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -301,3 +301,13 @@ margin-bottom: 0; } } + +.mirror-error-badge { + background-color: $error-bg; + border-radius: $border-radius-default; + color: $white-light; +} + +.push-pull-table { + margin-top: 1em; +} diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb new file mode 100644 index 00000000000..dc611ea7408 --- /dev/null +++ b/app/helpers/mirror_helper.rb @@ -0,0 +1,5 @@ +module MirrorHelper + def mirrors_form_data_attributes + { project_mirror_endpoint: project_mirror_path(@project) } + end +end \ No newline at end of file diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index 64f0fde30cf..117d6c3db6e 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -1,10 +1,11 @@ .account-well.prepend-top-default.append-bottom-default %ul %li - The repository must be accessible over http://, https://, ssh:// or git://. - %li - Include the username in the URL if required: https://username@gitlab.company.com/group/project.git. - %li - The update action will time out after 10 minutes. For big repositories, use a clone/push combination. - %li - The Git LFS objects will not be synced. + = _('The repository must be accessible over http://, + https://, ssh:// and git://.').html_safe + %li= _('The update action will time out after 10 minutes. For big repositories, use a clone/push combination.') + %li= _('The Git LFS objects will not be synced.').html_safe + %li + = _('This user will be the author of all events in the activity feed that are the result of an update, + like new branches being created or new commits being pushed to existing branches.') + diff --git a/app/views/projects/mirrors/_push.html.haml b/app/views/projects/mirrors/_push.html.haml deleted file mode 100644 index 08375e09816..00000000000 --- a/app/views/projects/mirrors/_push.html.haml +++ /dev/null @@ -1,50 +0,0 @@ -- expanded = Rails.env.test? -%section.settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) } - .settings-header - %h4 - Push to a remote repository - %button.btn.js-settings-toggle - = expanded ? 'Collapse' : 'Expand' - %p - Set up the remote repository that you want to update with the content of the current repository - every time someone pushes to it. - = link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository'), target: '_blank' - .settings-content - = form_for @project, url: project_mirror_path(@project) do |f| - %div - = form_errors(@project) - = render "shared/remote_mirror_update_button", remote_mirror: @remote_mirror - - if @remote_mirror.last_error.present? - .panel.panel-danger - .panel-heading - - if @remote_mirror.last_update_at - The remote repository failed to update #{time_ago_with_tooltip(@remote_mirror.last_update_at)}. - - else - The remote repository failed to update. - - - if @remote_mirror.last_successful_update_at - Last successful update #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}. - .panel-body - %pre - :preserve - #{h(@remote_mirror.last_error.strip)} - = f.fields_for :remote_mirrors, @remote_mirror do |rm_form| - .form-group - = rm_form.check_box :enabled, class: "float-left" - .prepend-left-20 - = rm_form.label :enabled, "Remote mirror repository", class: "label-bold append-bottom-0" - %p.light.append-bottom-0 - Automatically update the remote mirror's branches, tags, and commits from this repository every time someone pushes to it. - .form-group.has-feedback - = rm_form.label :url, "Git repository URL", class: "label-bold" - = rm_form.text_field :url, class: "form-control", placeholder: 'https://username:password@gitlab.company.com/group/project.git' - - = render "projects/mirrors/instructions" - - .form-group - = rm_form.check_box :only_protected_branches, class: 'float-left' - .prepend-left-20 - = rm_form.label :only_protected_branches, class: 'label-bold' - = link_to icon('question-circle'), help_page_path('user/project/protected_branches') - - = f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror' diff --git a/app/views/projects/mirrors/_push_pull.html.haml b/app/views/projects/mirrors/_push_pull.html.haml new file mode 100644 index 00000000000..c082171f9ff --- /dev/null +++ b/app/views/projects/mirrors/_push_pull.html.haml @@ -0,0 +1,70 @@ +- expanded = Rails.env.test? +- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') +- can_push = can?(current_user, :admin_remote_mirror, @project) +- can_pull = can?(current_user, :admin_mirror, @project) +- options = [] +- options.unshift([_('Pull'), 'pull']) if can_pull +- options.unshift([_('Push'), 'push']) if can_push + +%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) } + .settings-header + %h4= _('Mirroring repositories') + %button.btn.js-settings-toggle + = expanded ? _('Collapse') : _('Expand') + %p + = _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.') + = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank' + + .settings-content + = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f| + .panel.panel-default + .panel-heading + %h3.panel-title= _('Mirror a repository') + .panel-body + %div= form_errors(@project) + + .form-group.has-feedback + = label_tag :url, _('Git repository URL'), class: 'label-light' + = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+" + + = render 'projects/mirrors/instructions' + + = render_if_exists 'projects/mirrors/direction_dropdown', options: options + + = render 'projects/mirrors/push_pull_form', can_push: can_push, can_pull: can_pull, f: f + + .form-check.append-bottom-10 + = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input' + = label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label' + = link_to icon('question-circle'), help_page_path('user/project/protected_branches') + + .panel-footer + = f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror + + .panel.panel-default + .table-responsive + %table.table.push-pull-table + %thead + %tr + %th + = _('Mirrored repositories') + = render_if_exists 'projects/mirrors/mirrored_repositories_count' + %th= _('Direction') + %th= _('Last update') + %th + %th + %tbody.js-mirrors-table-body + = render_if_exists 'projects/mirrors/table_pull_row' + - @project.remote_mirrors.each_with_index do |mirror, index| + - if mirror.enabled + %tr + %td= mirror.safe_url + %td= _('Push') + %td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never') + %td + - if mirror.last_error.present? + .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') + %td + .btn-group.mirror-actions-group{ role: 'group' } + = render 'shared/remote_mirror_update_button', remote_mirror: mirror + %button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') diff --git a/app/views/projects/mirrors/_push_pull_form.html.haml b/app/views/projects/mirrors/_push_pull_form.html.haml new file mode 100644 index 00000000000..f7203103432 --- /dev/null +++ b/app/views/projects/mirrors/_push_pull_form.html.haml @@ -0,0 +1,6 @@ +- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') + += f.fields_for :remote_mirrors, @remote_mirror do |rm_f| + = rm_f.hidden_field :enabled, value: '1' + = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+" + = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden' \ No newline at end of file diff --git a/app/views/projects/mirrors/_show.html.haml b/app/views/projects/mirrors/_show.html.haml index de77701a373..ae5022bb243 100644 --- a/app/views/projects/mirrors/_show.html.haml +++ b/app/views/projects/mirrors/_show.html.haml @@ -1,3 +1 @@ -- if can?(current_user, :admin_remote_mirror, @project) - = render 'projects/mirrors/push' - += render 'projects/mirrors/push_pull' \ No newline at end of file diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml index 34de1c0695f..f32cff18fa8 100644 --- a/app/views/shared/_remote_mirror_update_button.html.haml +++ b/app/views/shared/_remote_mirror_update_button.html.haml @@ -1,13 +1,6 @@ -- if @project.has_remote_mirror? - .append-bottom-default - - if remote_mirror.update_in_progress? - %span.btn.disabled - = icon("refresh spin") - Updating… - - else - = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn" do - = icon("refresh") - Update Now - - if @remote_mirror.last_successful_update_at - %p.inline.prepend-left-10 - Successfully updated #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}. +- if remote_mirror.update_in_progress? + %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') } + = icon("refresh spin") +- else + = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do + = icon("refresh") diff --git a/db/schema.rb b/db/schema.rb index 1a5555fb3a4..84f751ea01e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180704204006) do +ActiveRecord::Schema.define(version: 20180718005113) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -168,6 +168,7 @@ ActiveRecord::Schema.define(version: 20180704204006) do t.boolean "enforce_terms", default: false t.boolean "mirror_available", default: true, null: false t.boolean "hide_third_party_offers", default: false, null: false + t.boolean "instance_statistics_visibility_private", default: false, null: false end create_table "audit_events", force: :cascade do |t| @@ -326,10 +327,10 @@ ActiveRecord::Schema.define(version: 20180704204006) do t.integer "auto_canceled_by_id" t.boolean "retried" t.integer "stage_id" - t.integer "artifacts_file_store" - t.integer "artifacts_metadata_store" t.boolean "protected" t.integer "failure_reason" + t.integer "artifacts_file_store" + t.integer "artifacts_metadata_store" end add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree @@ -386,13 +387,13 @@ ActiveRecord::Schema.define(version: 20180704204006) do t.integer "project_id", null: false t.integer "job_id", null: false t.integer "file_type", null: false - t.integer "file_store" t.integer "size", limit: 8 t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" t.binary "file_sha256" + t.integer "file_store" end add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree -- cgit v1.2.1 From f4343dd037f53fa584e06c52729293a0fa52fc69 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 25 Jul 2018 12:14:27 +0100 Subject: Remove debugger --- .../javascripts/pages/projects/settings/repository/show/push_pull.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js b/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js index 7ccba4a39b5..242ee73e2f8 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js @@ -40,8 +40,6 @@ export default class PushPull { const $target = $(event.currentTarget); let payload = existingPayload; - debugger; - if (!payload) { payload = { project: { -- cgit v1.2.1 From ce6c97ee56be7db7a6ffccef33b4e75d175a3986 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 25 Jul 2018 12:19:55 +0100 Subject: Revert some schema.rb changes --- db/schema.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 84f751ea01e..97cc9404719 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -327,10 +327,10 @@ ActiveRecord::Schema.define(version: 20180718005113) do t.integer "auto_canceled_by_id" t.boolean "retried" t.integer "stage_id" - t.boolean "protected" - t.integer "failure_reason" t.integer "artifacts_file_store" t.integer "artifacts_metadata_store" + t.boolean "protected" + t.integer "failure_reason" end add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree @@ -387,13 +387,13 @@ ActiveRecord::Schema.define(version: 20180718005113) do t.integer "project_id", null: false t.integer "job_id", null: false t.integer "file_type", null: false + t.integer "file_store" t.integer "size", limit: 8 t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" t.binary "file_sha256" - t.integer "file_store" end add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree -- cgit v1.2.1 From a68f24164ef07188ba2abcb81c1debdb9df5fc08 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 25 Jul 2018 12:20:46 +0100 Subject: Revert all schema.rb changes --- db/schema.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 97cc9404719..1a5555fb3a4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180718005113) do +ActiveRecord::Schema.define(version: 20180704204006) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -168,7 +168,6 @@ ActiveRecord::Schema.define(version: 20180718005113) do t.boolean "enforce_terms", default: false t.boolean "mirror_available", default: true, null: false t.boolean "hide_third_party_offers", default: false, null: false - t.boolean "instance_statistics_visibility_private", default: false, null: false end create_table "audit_events", force: :cascade do |t| -- cgit v1.2.1 From 56744742a9ca7f3fa2b92f31cddee212736008f3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 25 Jul 2018 12:29:50 +0100 Subject: port ProjectMirrorSerializer --- app/serializers/project_mirror_serializer.rb | 3 +++ spec/serializers/project_mirror_serializer_spec.rb | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 app/serializers/project_mirror_serializer.rb create mode 100644 spec/serializers/project_mirror_serializer_spec.rb diff --git a/app/serializers/project_mirror_serializer.rb b/app/serializers/project_mirror_serializer.rb new file mode 100644 index 00000000000..7e9ba592252 --- /dev/null +++ b/app/serializers/project_mirror_serializer.rb @@ -0,0 +1,3 @@ +class ProjectMirrorSerializer < BaseSerializer + entity ProjectMirrorEntity +end diff --git a/spec/serializers/project_mirror_serializer_spec.rb b/spec/serializers/project_mirror_serializer_spec.rb new file mode 100644 index 00000000000..5e47163532a --- /dev/null +++ b/spec/serializers/project_mirror_serializer_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe ProjectMirrorSerializer do + it 'represents ProjectMirror entities' do + expect(described_class.entity_class).to eq(ProjectMirrorEntity) + end +end -- cgit v1.2.1 From 8680038c9f8edfe00955f8e80bec195a75070aa8 Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Tue, 26 Jun 2018 23:59:44 -0700 Subject: Clarrify use of regression label --- PROCESS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/PROCESS.md b/PROCESS.md index a06ddb68b77..af66a343f13 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -244,6 +244,12 @@ ping the Engineering Manager or the Product Manager for the relative area to make them aware of the issue earlier. They will analyze the priority and change it if needed. +A ~regression label implies that a regress in functionality happened, a functionality which worked previously but no longer works currently. +The ~regression label is not removed as part of the "Rescheduling" process. If an issue is indeed a regression, it should carry such context forward until it's fully resolved. +A ~regression label on a ~bug tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule. + +The milestone of a ~regression is used to schedule when the fix will be delivered. The creation time of a ~regression tells us which release it was found in. + ## Release retrospective and kickoff - [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective) -- cgit v1.2.1 From ed79f338aa74859cd5e554e8948432d6156d847d Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Thu, 28 Jun 2018 08:01:54 -0700 Subject: Add excerpt for non-regressions --- PROCESS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PROCESS.md b/PROCESS.md index af66a343f13..ef25efb61ff 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -250,6 +250,8 @@ A ~regression label on a ~bug tells us that something worked before and it needs The milestone of a ~regression is used to schedule when the fix will be delivered. The creation time of a ~regression tells us which release it was found in. +A ~regression label does not apply to ~bugs for new features. That by the definition above are not regressions. + ## Release retrospective and kickoff - [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective) -- cgit v1.2.1 From 80beb2716c84ff482dd54a158bc1777d764fcd19 Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Fri, 13 Jul 2018 20:44:13 -0700 Subject: Laydown process for bugs and regressions --- PROCESS.md | 70 +++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index ef25efb61ff..2416ec93275 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -201,30 +201,53 @@ you can ask for an exception to be made. Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one. +## Bugs + +A ~bug ia a defect, error, failure which causes the system to behave incorrectly or preventing it from fulfill the product requirements. + +The level of impact of a ~bug can vary from blocking a whole functionality or a feature usability bug. + +A bug should always be linked to a severity level. Refer to our [severity levels](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#severity-labels) + +### Managing a Bug + +When a regression is created: +1. Create an issue describing the problem in the most detailed way possible. +1. If possible, provide links to real examples and how to reproduce the problem. +1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels), + the [subject label](../CONTRIBUTING.md#subject-labels) + and any other label that may apply in the specific case +1. Add the ~bug label +1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels). +1. From the Severity and Priority level the Engineering Manager then decides which milestone to set on the bug. + ## Regressions -A regression for a particular monthly release is a bug that exists in that -release, but wasn't present in the release before. This includes bugs in -features that were only added in that monthly release. Every regression **must** -have the milestone of the release it was introduced in - if a regression doesn't -have a milestone, it might be 'just' a bug! +A ~regression implies that a previously **verified working functionality** no longer works. +Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress. + +The regression label does not apply to ~bugs for new features which functionality was **never verified as working**. +That by definition are not regressions. The ~regression label is not removed as part of any rescheduling process. +If an issue is indeed a regression, it should carry such context forward until it's fully resolved. -For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly, -then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow -reintroduces the bug, then this bug is still a regression in 10.5. +A regression should always have the `regression:xx.x` label on it to designate when it was introduced. -Because GitLab.com runs release candidates of new releases, a regression can be -reported in a release before its 'official' release date on the 22nd of the -month. When we say 'the most recent monthly release', this can refer to either -the version currently running on GitLab.com, or the most recent version -available in the package repositories. +### Managing a Regression -### How to manage a regression +**Prioritization** -Regressions are very important, and they should be considered high priority -issues that should be solved as soon as possible, especially if they affect -users. Despite that, ~regression label itself does not imply when the issue -will be scheduled. +A ~regression label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule. + +Regressions should be considered high priority issues that should be solved as soon as possible, especially if they have severe impact on users. + +We give higher priority to regressions that affected the last recent monthly release and the current release candidates. +The two scenarios below can [by pass the exception request in the release process](LINK_HERE_TO_RM_DOC) +* A regression in the **Last recent monthly release** + * If in 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works this is regression for 11.0. The issue should have the `regression:11.0` label. + * **Note:** When we say `the last recent monthly release`, this can refer to either the version currently running on GitLab.com, or the most recent version available in the package repositories. +* A regression in the **Current release candidates** + * If in 11.1-RC3 w + * **NOte:** Because GitLab.com runs release candidates of new releases, a regression can be reported in a release before its 'official' release date on the 22nd of the month. When a regression is found: 1. Create an issue describing the problem in the most detailed way possible @@ -239,18 +262,9 @@ When a regression is found: 1. If the regression was introduced in the previous release, label with ~"Next Patch Release" 1. If the regression is an ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. -When a new issue is found, the fix should start as soon as possible. You can -ping the Engineering Manager or the Product Manager for the relative area to -make them aware of the issue earlier. They will analyze the priority and change -it if needed. - -A ~regression label implies that a regress in functionality happened, a functionality which worked previously but no longer works currently. -The ~regression label is not removed as part of the "Rescheduling" process. If an issue is indeed a regression, it should carry such context forward until it's fully resolved. -A ~regression label on a ~bug tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule. +When a new issue is found, the fix should start as soon as possible. You can ping the Engineering Manager or the Product Manager for the relative area to make them aware of the issue earlier. They will analyze the priority and change it if needed. -The milestone of a ~regression is used to schedule when the fix will be delivered. The creation time of a ~regression tells us which release it was found in. -A ~regression label does not apply to ~bugs for new features. That by the definition above are not regressions. ## Release retrospective and kickoff -- cgit v1.2.1 From ec85a2a3ac158bdb71488af44b86c2a84c5d174b Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Fri, 13 Jul 2018 20:58:56 -0700 Subject: Simplify regression triage flow --- PROCESS.md | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 2416ec93275..fcedec34e09 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -201,15 +201,23 @@ you can ask for an exception to be made. Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one. -## Bugs +## Defects -A ~bug ia a defect, error, failure which causes the system to behave incorrectly or preventing it from fulfill the product requirements. +We categorize defects into 2 main categories, a ~bug and a ~regression. + +Whether the defect is a bug or a regression, the triage process should start as soon as possible. +You can ping the Engineering Manager or the Product Manager for the relative area to make them aware of the issue earlier. +They will analyze and prioritize the work as needed. -The level of impact of a ~bug can vary from blocking a whole functionality or a feature usability bug. +### Bugs + +A ~bug ia a defect, error, failure which causes the system to behave incorrectly or preventing it from fulfill the product requirements. -A bug should always be linked to a severity level. Refer to our [severity levels](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#severity-labels) +The level of impact of a ~bug can vary from blocking a whole functionality +or a feature usability bug. A bug should always be linked to a severity level. +Refer to our [severity levels](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#severity-labels) -### Managing a Bug +#### Managing a Bug When a regression is created: 1. Create an issue describing the problem in the most detailed way possible. @@ -221,18 +229,18 @@ When a regression is created: 1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels). 1. From the Severity and Priority level the Engineering Manager then decides which milestone to set on the bug. -## Regressions +### Regressions A ~regression implies that a previously **verified working functionality** no longer works. Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress. -The regression label does not apply to ~bugs for new features which functionality was **never verified as working**. +The regression label does not apply to ~bugs for new features to which functionality was **never verified as working**. That by definition are not regressions. The ~regression label is not removed as part of any rescheduling process. If an issue is indeed a regression, it should carry such context forward until it's fully resolved. A regression should always have the `regression:xx.x` label on it to designate when it was introduced. -### Managing a Regression +#### Managing a Regression **Prioritization** @@ -243,11 +251,11 @@ Regressions should be considered high priority issues that should be solved as s We give higher priority to regressions that affected the last recent monthly release and the current release candidates. The two scenarios below can [by pass the exception request in the release process](LINK_HERE_TO_RM_DOC) * A regression in the **Last recent monthly release** - * If in 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works this is regression for 11.0. The issue should have the `regression:11.0` label. - * **Note:** When we say `the last recent monthly release`, this can refer to either the version currently running on GitLab.com, or the most recent version available in the package repositories. + * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works this is regression for 11.0. The issue should have the `regression:11.0` label. + * *Note:* When we say `the last recent monthly release`, this can refer to either the version currently running on GitLab.com, or the most recent version available in the package repositories. * A regression in the **Current release candidates** - * If in 11.1-RC3 w - * **NOte:** Because GitLab.com runs release candidates of new releases, a regression can be reported in a release before its 'official' release date on the 22nd of the month. + * **Example:** In 11.1-RC3 we shipped a new feature which has been verified as working. Then in 11.1-RC5 the feature no longer works this is regression for 11.1. The issue should have the `regression:11.1` label. + * *Note:* Because GitLab.com runs release candidates of new releases, a regression can be reported in a release before its 'official' release date on the 22nd of the month. When a regression is found: 1. Create an issue describing the problem in the most detailed way possible @@ -257,14 +265,12 @@ When a regression is found: and any other label that may apply in the specific case 1. Add the ~bug and ~regression labels 1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels). -1. If the regression is either an ~S1, ~S2 or ~S3 severity, label the regression with the current milestone as it should be fixed in the current milestone. - 1. If the regression was introduced in an RC of the current release, label with ~Deliverable - 1. If the regression was introduced in the previous release, label with ~"Next Patch Release" -1. If the regression is an ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. - -When a new issue is found, the fix should start as soon as possible. You can ping the Engineering Manager or the Product Manager for the relative area to make them aware of the issue earlier. They will analyze the priority and change it if needed. - - +1. Determine the release that the regression affects. Add the `regression:xx.x` label. +1. If the `regression:xx.x` is the **Current release**, schedule it for the current milestone as it should be fixed in the current milestone. Scope it with ~Deliverable. +1. If the `regression:xx.x` is the **Last monthly release**, schedule it for the current milestone as it should be fixed in the current milestone. Scope it with ~"Next Patch Release". +1. If the `regression:xx.x` is older than the **Current release** and **Last monthly release**: + 1. If the regression is an ~S1 severity, label the regression with the current milestone as it should be fixed in the current milestone. Scope it with ~Stretch. + 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. ## Release retrospective and kickoff -- cgit v1.2.1 From e3b005fe425b55b7aee94105b4ea92e14a3ee6c2 Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Fri, 13 Jul 2018 21:17:59 -0700 Subject: Fixed links and clarrified after the 7th section Regressions that can bypass the exception requests are the ones which `regression:xx.x` is the last recent monthly release or the current release. --- PROCESS.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index fcedec34e09..b773458fccc 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -15,8 +15,11 @@ - [Between the 1st and the 7th](#between-the-1st-and-the-7th) - [On the 7th](#on-the-7th) - [After the 7th](#after-the-7th) -- [Regressions](#regressions) - - [How to manage a regression](#how-to-manage-a-regression) +- [Defects](#defects) + - [Bugs](#bugs) + - [Managing a bug](#managing-a-bug) + - [Regressions](#regressions) + - [Managing a regression](#managing-a-regression) - [Release retrospective and kickoff](#release-retrospective-and-kickoff) - [Retrospective](#retrospective) - [Kickoff](#kickoff) @@ -168,7 +171,7 @@ information, see Once the stable branch is frozen, the only MRs that can be cherry-picked into the stable branch are: -* Fixes for [regressions](#regressions) +* Fixes for [regressions](#regressions), where `regression:xx.x` is the last recent monthly release or the current release. * Fixes for security issues * Fixes or improvements to automated QA scenarios * Documentation updates for changes in the same release @@ -242,13 +245,11 @@ A regression should always have the `regression:xx.x` label on it to designate w #### Managing a Regression -**Prioritization** - A ~regression label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule. Regressions should be considered high priority issues that should be solved as soon as possible, especially if they have severe impact on users. -We give higher priority to regressions that affected the last recent monthly release and the current release candidates. +**Prioritization** We give higher priority to regressions that affected the last recent monthly release and the current release candidates. The two scenarios below can [by pass the exception request in the release process](LINK_HERE_TO_RM_DOC) * A regression in the **Last recent monthly release** * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works this is regression for 11.0. The issue should have the `regression:11.0` label. -- cgit v1.2.1 From 347ecbb6887e86e53b1e01c593ef3a6302a35b87 Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Mon, 16 Jul 2018 16:18:36 -0700 Subject: Collaspe regression under bugs - Bugs are the parent section - Use the same workflow but branch off bugs / regression sections - Addressed review comments --- PROCESS.md | 80 ++++++++++++++++++++++++-------------------------------------- 1 file changed, 31 insertions(+), 49 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index b773458fccc..a97e3beb92e 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -15,11 +15,9 @@ - [Between the 1st and the 7th](#between-the-1st-and-the-7th) - [On the 7th](#on-the-7th) - [After the 7th](#after-the-7th) -- [Defects](#defects) - - [Bugs](#bugs) - - [Managing a bug](#managing-a-bug) +- [Bugs](#bugs) - [Regressions](#regressions) - - [Managing a regression](#managing-a-regression) + - [Managing bugs](#managing-bugs) - [Release retrospective and kickoff](#release-retrospective-and-kickoff) - [Retrospective](#retrospective) - [Kickoff](#kickoff) @@ -171,7 +169,7 @@ information, see Once the stable branch is frozen, the only MRs that can be cherry-picked into the stable branch are: -* Fixes for [regressions](#regressions), where `regression:xx.x` is the last recent monthly release or the current release. +* Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing a regression](#managing-a-regression). * Fixes for security issues * Fixes or improvements to automated QA scenarios * Documentation updates for changes in the same release @@ -204,74 +202,58 @@ you can ask for an exception to be made. Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one. -## Defects +## Bugs -We categorize defects into 2 main categories, a ~bug and a ~regression. - -Whether the defect is a bug or a regression, the triage process should start as soon as possible. -You can ping the Engineering Manager or the Product Manager for the relative area to make them aware of the issue earlier. -They will analyze and prioritize the work as needed. - -### Bugs - -A ~bug ia a defect, error, failure which causes the system to behave incorrectly or preventing it from fulfill the product requirements. +A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling from fulfill the product requirements. The level of impact of a ~bug can vary from blocking a whole functionality or a feature usability bug. A bug should always be linked to a severity level. Refer to our [severity levels](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#severity-labels) -#### Managing a Bug - -When a regression is created: -1. Create an issue describing the problem in the most detailed way possible. -1. If possible, provide links to real examples and how to reproduce the problem. -1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels), - the [subject label](../CONTRIBUTING.md#subject-labels) - and any other label that may apply in the specific case -1. Add the ~bug label -1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels). -1. From the Severity and Priority level the Engineering Manager then decides which milestone to set on the bug. +Whether the bug is also a regression or not, the triage process should start as soon as possible. +Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed. ### Regressions A ~regression implies that a previously **verified working functionality** no longer works. Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress. +The label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule. -The regression label does not apply to ~bugs for new features to which functionality was **never verified as working**. -That by definition are not regressions. The ~regression label is not removed as part of any rescheduling process. -If an issue is indeed a regression, it should carry such context forward until it's fully resolved. +The regression label does not apply to ~bugs for new features for which functionality was **never verified as working**. +These, by definition, are not regressions. A regression should always have the `regression:xx.x` label on it to designate when it was introduced. -#### Managing a Regression - -A ~regression label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule. - Regressions should be considered high priority issues that should be solved as soon as possible, especially if they have severe impact on users. +### Managing bugs + **Prioritization** We give higher priority to regressions that affected the last recent monthly release and the current release candidates. -The two scenarios below can [by pass the exception request in the release process](LINK_HERE_TO_RM_DOC) -* A regression in the **Last recent monthly release** - * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works this is regression for 11.0. The issue should have the `regression:11.0` label. +The two scenarios below can [bypass the exception request in the release process](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md#after-the-7th), where the affected regression version matches the current monthly release version. +* A regression which worked in the **Last monthly release** + * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label. * *Note:* When we say `the last recent monthly release`, this can refer to either the version currently running on GitLab.com, or the most recent version available in the package repositories. -* A regression in the **Current release candidates** - * **Example:** In 11.1-RC3 we shipped a new feature which has been verified as working. Then in 11.1-RC5 the feature no longer works this is regression for 11.1. The issue should have the `regression:11.1` label. +* A regression which worked in the **Current release candidates** + * **Example:** In 11.1-RC3 we shipped a new feature which has been verified as working. Then in 11.1-RC5 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label. * *Note:* Because GitLab.com runs release candidates of new releases, a regression can be reported in a release before its 'official' release date on the 22nd of the month. -When a regression is found: -1. Create an issue describing the problem in the most detailed way possible -1. If possible, provide links to real examples and how to reproduce the problem +When a bug is found: +1. Create an issue describing the problem in the most detailed way possible. +1. If possible, provide links to real examples and how to reproduce the problem. 1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels), the [subject label](../CONTRIBUTING.md#subject-labels) and any other label that may apply in the specific case -1. Add the ~bug and ~regression labels -1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels). -1. Determine the release that the regression affects. Add the `regression:xx.x` label. -1. If the `regression:xx.x` is the **Current release**, schedule it for the current milestone as it should be fixed in the current milestone. Scope it with ~Deliverable. -1. If the `regression:xx.x` is the **Last monthly release**, schedule it for the current milestone as it should be fixed in the current milestone. Scope it with ~"Next Patch Release". -1. If the `regression:xx.x` is older than the **Current release** and **Last monthly release**: - 1. If the regression is an ~S1 severity, label the regression with the current milestone as it should be fixed in the current milestone. Scope it with ~Stretch. - 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. +1. If the ~bug is a **regression**, add the ~regression label. +1. Notify the respective Engineering Manager to evaluate the Severity and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels) and [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels). +The counterpart Product Manager is included to weigh-in on prioritization as needed. +1. If the ~bug is **NOT** a regression: + 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied. +1. If the bug is a ~regression: + 1. Determine the release that the regression affects. Add the corresponding `regression:xx.x` label. See **Prioritization** section above. + 1. If the affected version `xx.x` in `regression:xx.x` is the **Current release**, schedule it for the current milestone as it should be fixed in the current milestone. Scope it with ~Deliverable. + 1. If the affected version `xx.x` in `regression:xx.x` is older than the **Current release** + 1. If the regression is an ~S1 severity, label the regression with the current milestone as it should be fixed in the current milestone. Scope it with ~"Next Patch Release" or ~Stretch. + 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. ## Release retrospective and kickoff -- cgit v1.2.1 From 8390355ba8b68ccd2a35cce2f121bdc10d63c831 Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Wed, 18 Jul 2018 19:47:10 +0000 Subject: Fix typo --- PROCESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PROCESS.md b/PROCESS.md index a97e3beb92e..fd0c45f3b58 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -204,7 +204,7 @@ Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/genera ## Bugs -A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling from fulfill the product requirements. +A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling the product requirements. The level of impact of a ~bug can vary from blocking a whole functionality or a feature usability bug. A bug should always be linked to a severity level. -- cgit v1.2.1 From 4502a276abe77544662a8fd09f8d2d857cb3dcb3 Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Thu, 19 Jul 2018 20:29:40 -0700 Subject: Simplified process and address review comments --- PROCESS.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index fd0c45f3b58..08b5751f3e9 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -208,7 +208,7 @@ A ~bug is a defect, error, failure which causes the system to behave incorrectly The level of impact of a ~bug can vary from blocking a whole functionality or a feature usability bug. A bug should always be linked to a severity level. -Refer to our [severity levels](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#severity-labels) +Refer to our [severity levels](../CONTRIBUTING.md#severity-labels) Whether the bug is also a regression or not, the triage process should start as soon as possible. Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed. @@ -228,7 +228,7 @@ Regressions should be considered high priority issues that should be solved as s ### Managing bugs -**Prioritization** We give higher priority to regressions that affected the last recent monthly release and the current release candidates. +**Prioritization:** We give higher priority to regressions which worked in the last recent monthly release and the current release candidates. The two scenarios below can [bypass the exception request in the release process](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md#after-the-7th), where the affected regression version matches the current monthly release version. * A regression which worked in the **Last monthly release** * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label. @@ -243,17 +243,18 @@ When a bug is found: 1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels), the [subject label](../CONTRIBUTING.md#subject-labels) and any other label that may apply in the specific case -1. If the ~bug is a **regression**, add the ~regression label. -1. Notify the respective Engineering Manager to evaluate the Severity and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels) and [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels). +1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed. +1. If the ~bug is a **regression**, determine the release that the regression affects and add the corresponding `regression:xx.x` label. + 1. If the affected release version can't be determined, add the generic ~regression label for the time being. 1. If the ~bug is **NOT** a regression: - 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied. + 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied. 1. If the bug is a ~regression: - 1. Determine the release that the regression affects. Add the corresponding `regression:xx.x` label. See **Prioritization** section above. - 1. If the affected version `xx.x` in `regression:xx.x` is the **Current release**, schedule it for the current milestone as it should be fixed in the current milestone. Scope it with ~Deliverable. - 1. If the affected version `xx.x` in `regression:xx.x` is older than the **Current release** - 1. If the regression is an ~S1 severity, label the regression with the current milestone as it should be fixed in the current milestone. Scope it with ~"Next Patch Release" or ~Stretch. - 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. + 1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, schedule it for the current milestone as it should be fixed immediately. + 1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above. + 1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release** + 1. If the regression is an ~S1 severity, schedule it to be fixed in the current milestone. We would like to fix the highest severity regression as soon as we can. + 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. ## Release retrospective and kickoff -- cgit v1.2.1 From 8cff6f8e92b2548cda9432c0731ab575b8fa0ccf Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Wed, 25 Jul 2018 16:11:28 -0700 Subject: Refactored if logic for regression. --- PROCESS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 08b5751f3e9..70214f1c194 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -245,11 +245,11 @@ When a bug is found: and any other label that may apply in the specific case 1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed. -1. If the ~bug is a **regression**, determine the release that the regression affects and add the corresponding `regression:xx.x` label. - 1. If the affected release version can't be determined, add the generic ~regression label for the time being. 1. If the ~bug is **NOT** a regression: - 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied. + 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied 1. If the bug is a ~regression: + 1. Determine the release that the regression affects and add the corresponding `regression:xx.x` label. + 1. If the affected release version can't be determined, add the generic ~regression label for the time being. 1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, schedule it for the current milestone as it should be fixed immediately. 1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above. 1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release** -- cgit v1.2.1 From 46a54123fc1f972f73002fe9c3f78963fe1e89ae Mon Sep 17 00:00:00 2001 From: Mek Stittri Date: Thu, 26 Jul 2018 23:55:53 -0700 Subject: Update scheduling language to be less descriptive --- PROCESS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 70214f1c194..5f50d472bd7 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -169,7 +169,7 @@ information, see Once the stable branch is frozen, the only MRs that can be cherry-picked into the stable branch are: -* Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing a regression](#managing-a-regression). +* Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing bugs](#managing-bugs) section. * Fixes for security issues * Fixes or improvements to automated QA scenarios * Documentation updates for changes in the same release @@ -228,7 +228,7 @@ Regressions should be considered high priority issues that should be solved as s ### Managing bugs -**Prioritization:** We give higher priority to regressions which worked in the last recent monthly release and the current release candidates. +**Prioritization:** We give higher priority to regressions on features that worked in the last recent monthly release and the current release candidates. The two scenarios below can [bypass the exception request in the release process](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md#after-the-7th), where the affected regression version matches the current monthly release version. * A regression which worked in the **Last monthly release** * **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label. @@ -246,15 +246,15 @@ When a bug is found: 1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed. 1. If the ~bug is **NOT** a regression: - 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied + 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied. 1. If the bug is a ~regression: 1. Determine the release that the regression affects and add the corresponding `regression:xx.x` label. 1. If the affected release version can't be determined, add the generic ~regression label for the time being. - 1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, schedule it for the current milestone as it should be fixed immediately. + 1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, it's recommended to schedule the fix for the current milestone. 1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above. 1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release** - 1. If the regression is an ~S1 severity, schedule it to be fixed in the current milestone. We would like to fix the highest severity regression as soon as we can. - 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager. + 1. If the regression is an ~S1 severity, it's recommended to schedule the fix for the current milestone. We would like to fix the highest severity regression as soon as we can. + 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of the Engineering Manager and Product Manager. ## Release retrospective and kickoff -- cgit v1.2.1 From 34e912b538b54619920b714b5177798597758808 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 31 Jul 2018 00:48:59 +0530 Subject: Use a hash to memoize readable_project_ids with user objects as keys --- lib/banzai/reference_parser/project_parser.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index a329af42681..2a33b00ddbd 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -13,16 +13,15 @@ module Banzai # Returns an Array of Project ids that can be read by the given user. # - # projects - The projects to reduce down to those readable by the user. # user - The User for which to check the projects - def readable_project_ids_for(projects, user) - strong_memoize(:readable_project_ids_for) do - Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) - end + def readable_project_ids_for(user) + @project_ids_by_user ||= {} + @project_ids_by_user[user] ||= + Project.public_or_visible_to_user(user).where("projects.id IN (?)", @projects_for_nodes.values.map(&:id)).pluck(:id) end def can_read_reference?(user, ref_project, node) - readable_project_ids_for(@projects_for_nodes.values, user).include?(ref_project.try(:id)) + readable_project_ids_for(user).include?(ref_project.try(:id)) end end end -- cgit v1.2.1 From 94dfd15e473d6d4775b07091ef5a878a8e365965 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 2 Aug 2018 00:08:22 +0100 Subject: re-port --- .../projects/settings/repository/show/index.js | 6 +- .../settings/repository/show/mirror_repos.js | 81 ++++++++++++++++++++++ .../projects/settings/repository/show/push_pull.js | 67 ------------------ app/assets/stylesheets/framework/buttons.scss | 4 ++ app/assets/stylesheets/framework/forms.scss | 2 +- app/assets/stylesheets/pages/settings.scss | 8 +++ app/views/projects/mirrors/_instructions.html.haml | 1 - app/views/projects/mirrors/_mirror_repos.html.haml | 72 +++++++++++++++++++ .../projects/mirrors/_mirror_repos_form.html.haml | 14 ++++ app/views/projects/mirrors/_push_pull.html.haml | 70 ------------------- .../projects/mirrors/_push_pull_form.html.haml | 6 -- app/views/projects/mirrors/_show.html.haml | 2 +- 12 files changed, 184 insertions(+), 149 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js delete mode 100644 app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js create mode 100644 app/views/projects/mirrors/_mirror_repos.html.haml create mode 100644 app/views/projects/mirrors/_mirror_repos_form.html.haml delete mode 100644 app/views/projects/mirrors/_push_pull.html.haml delete mode 100644 app/views/projects/mirrors/_push_pull_form.html.haml diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js index 923cd9efe87..78cf5406e43 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -1,9 +1,9 @@ import initForm from '../form'; -import PushPull from './push_pull'; +import MirrorRepos from './mirror_repos'; document.addEventListener('DOMContentLoaded', () => { initForm(); - const pushPullContainer = document.querySelector('.js-mirror-settings'); - if (pushPullContainer) new PushPull(pushPullContainer).init(); + const mirrorReposContainer = document.querySelector('.js-mirror-settings'); + if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init(); }); diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js new file mode 100644 index 00000000000..91070b3c6f2 --- /dev/null +++ b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js @@ -0,0 +1,81 @@ +import $ from 'jquery'; +import _ from 'underscore'; +import { __ } from '~/locale'; +import Flash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; + +export default class MirrorRepos { + constructor(container) { + this.$container = $(container); + this.$form = $('.js-mirror-form', this.$container); + this.$urlInput = $('.js-mirror-url', this.$form); + this.$protectedBranchesInput = $('.js-mirror-protected', this.$form); + this.$table = $('.js-mirrors-table-body', this.$container); + this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint'); + } + + init() { + this.registerUpdateListeners(); + this.initMirrorPush(); + + this.$table.on('click', '.js-delete-mirror', this.deleteMirror.bind(this)); + } + + updateUrl() { + let val = this.$urlInput.val(); + + if (this.$password) { + const password = this.$password.val(); + if (password) val = val.replace('@', `:${password}@`); + } + + $('.js-mirror-url-hidden', this.$form).val(val); + } + + updateProtectedBranches() { + const val = this.$protectedBranchesInput.get(0).checked + ? this.$protectedBranchesInput.val() + : '0'; + $('.js-mirror-protected-hidden', this.$form).val(val); + } + + registerUpdateListeners() { + this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200); + this.$urlInput.on('input', () => this.debouncedUpdateUrl()); + this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches()); + } + + initMirrorPush() { + this.$password = $('.js-password', this.$form); + this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl()); + } + + deleteMirror(event, existingPayload) { + const $target = $(event.currentTarget); + let payload = existingPayload; + + if (!payload) { + payload = { + project: { + remote_mirrors_attributes: { + id: $target.data('mirrorId'), + enabled: 0, + }, + }, + }; + } + + return axios + .put(this.mirrorEndpoint, payload) + .then(() => this.removeRow($target)) + .catch(() => Flash(__('Failed to remove mirror.'))); + } + + /* eslint-disable class-methods-use-this */ + removeRow($target) { + const row = $target.closest('tr'); + $('.js-delete-mirror', row).tooltip('hide'); + row.remove(); + } + /* eslint-enable class-methods-use-this */ +} diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js b/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js deleted file mode 100644 index 242ee73e2f8..00000000000 --- a/app/assets/javascripts/pages/projects/settings/repository/show/push_pull.js +++ /dev/null @@ -1,67 +0,0 @@ -import $ from 'jquery'; -import _ from 'underscore'; -import { __ } from '~/locale'; -import Flash from '~/flash'; -import axios from '~/lib/utils/axios_utils'; - -export default class PushPull { - constructor(container) { - this.$container = $(container); - this.$form = $('.js-mirror-form', this.$container); - this.$urlInput = $('.js-mirror-url', this.$form); - this.$protectedBranchesInput = $('.js-mirror-protected', this.$form); - this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint'); - this.$table = $('.js-mirrors-table-body', this.$container); - } - - init() { - this.registerUpdateListeners(); - - this.$table.on('click', '.js-delete-mirror', this.deleteMirror.bind(this)); - } - - updateUrl() { - $('.js-mirror-url-hidden', this.$form).val(this.$urlInput.val()); - } - - updateProtectedBranches() { - const val = this.$protectedBranchesInput.get(0).checked - ? this.$protectedBranchesInput.val() - : '0'; - $('.js-mirror-protected-hidden', this.$form).val(val); - } - - registerUpdateListeners() { - this.$urlInput.on('change', () => this.updateUrl()); - this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches()); - } - - deleteMirror(event, existingPayload) { - const $target = $(event.currentTarget); - let payload = existingPayload; - - if (!payload) { - payload = { - project: { - remote_mirrors_attributes: { - id: $target.data('mirrorId'), - enabled: 0, - }, - }, - }; - } - - return axios - .put(this.mirrorEndpoint, payload) - .then(() => this.removeRow($target)) - .catch(() => Flash(__('Failed to remove mirror.'))); - } - - /* eslint-disable class-methods-use-this */ - removeRow($target) { - const row = $target.closest('tr'); - $('.js-delete-mirror', row).tooltip('hide'); - row.remove(); - } - /* eslint-enable class-methods-use-this */ -} diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 646cedd79ed..a842ff92f8e 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -242,6 +242,10 @@ &:not(:last-child) { margin-right: 5px; } + + &.hide { + display: none; + } } } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index a10ff3eecb3..49a56cac14b 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -201,7 +201,7 @@ label { } .gl-show-field-errors { - .form-control { + .form-control:not(textarea) { height: 34px; } diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 03243581abf..fc5212cfade 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -311,3 +311,11 @@ .push-pull-table { margin-top: 1em; } + +.push-pull-table { + margin-top: 1em; + + .mirror-action-buttons { + padding-right: 0; + } +} diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index 117d6c3db6e..bc422543c88 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -8,4 +8,3 @@ %li = _('This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches.') - diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml new file mode 100644 index 00000000000..92770f15335 --- /dev/null +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -0,0 +1,72 @@ +- expanded = Rails.env.test? +- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') +- can_push = can?(current_user, :admin_remote_mirror, @project) +- can_pull = can?(current_user, :admin_mirror, @project) +- options = [] +- options.unshift([_('Pull'), 'pull']) if can_pull +- options.unshift([_('Push'), 'push']) if can_push + +%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) } + .settings-header + %h4= _('Mirroring repositories') + %button.btn.js-settings-toggle + = expanded ? _('Collapse') : _('Expand') + %p + = _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.') + = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank' + + .settings-content + = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f| + .panel.panel-default + .panel-heading + %h3.panel-title= _('Mirror a repository') + .panel-body + %div= form_errors(@project) + + .form-group.has-feedback + = label_tag :url, _('Git repository URL'), class: 'label-light' + = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+" + + = render 'projects/mirrors/instructions' + + .form-group + = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' + = select_tag :mirror_direction, options_for_select(options), class: 'form-control js-mirror-direction' + + = render 'projects/mirrors/mirror_repos_form', can_push: can_push, can_pull: can_pull, f: f + + .form-check.append-bottom-10 + = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input' + = label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label' + = link_to icon('question-circle'), help_page_path('user/project/protected_branches') + + .panel-footer + = f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror + + .panel.panel-default + .table-responsive + %table.table.push-pull-table + %thead + %tr + %th + = _('Mirrored repositories') + = render_if_exists 'projects/mirrors/mirrored_repositories_count' + %th= _('Direction') + %th= _('Last update') + %th + %th + %tbody.js-mirrors-table-body + = render_if_exists 'projects/mirrors/table_pull_row' + - @project.remote_mirrors.each_with_index do |mirror, index| + - if mirror.enabled + %tr + %td= mirror.safe_url + %td= _('Push') + %td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never') + %td + - if mirror.last_error.present? + .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') + %td.mirror-action-buttons + .btn-group.mirror-actions-group.pull-right{ role: 'group' } + = render 'shared/remote_mirror_update_button', remote_mirror: mirror + %button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml new file mode 100644 index 00000000000..3073ad67b62 --- /dev/null +++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml @@ -0,0 +1,14 @@ +- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') + += f.fields_for :remote_mirrors, @remote_mirror do |rm_f| + = rm_f.hidden_field :enabled, value: '1' + = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+" + = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden' + +.form-group + = label_tag :auth_method, _('Authentication method'), class: 'label-bold' + = select_tag :auth_method, options_for_select([[_('Password'), 'password']], 'password'), { class: "form-control js-auth-method", disabled: 'disabled' } + +.form-group + = label_tag :password, _('Password'), class: 'label-bold' + = text_field_tag :password, '', class: 'form-control js-password' diff --git a/app/views/projects/mirrors/_push_pull.html.haml b/app/views/projects/mirrors/_push_pull.html.haml deleted file mode 100644 index c082171f9ff..00000000000 --- a/app/views/projects/mirrors/_push_pull.html.haml +++ /dev/null @@ -1,70 +0,0 @@ -- expanded = Rails.env.test? -- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') -- can_push = can?(current_user, :admin_remote_mirror, @project) -- can_pull = can?(current_user, :admin_mirror, @project) -- options = [] -- options.unshift([_('Pull'), 'pull']) if can_pull -- options.unshift([_('Push'), 'push']) if can_push - -%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) } - .settings-header - %h4= _('Mirroring repositories') - %button.btn.js-settings-toggle - = expanded ? _('Collapse') : _('Expand') - %p - = _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.') - = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank' - - .settings-content - = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f| - .panel.panel-default - .panel-heading - %h3.panel-title= _('Mirror a repository') - .panel-body - %div= form_errors(@project) - - .form-group.has-feedback - = label_tag :url, _('Git repository URL'), class: 'label-light' - = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+" - - = render 'projects/mirrors/instructions' - - = render_if_exists 'projects/mirrors/direction_dropdown', options: options - - = render 'projects/mirrors/push_pull_form', can_push: can_push, can_pull: can_pull, f: f - - .form-check.append-bottom-10 - = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input' - = label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label' - = link_to icon('question-circle'), help_page_path('user/project/protected_branches') - - .panel-footer - = f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror - - .panel.panel-default - .table-responsive - %table.table.push-pull-table - %thead - %tr - %th - = _('Mirrored repositories') - = render_if_exists 'projects/mirrors/mirrored_repositories_count' - %th= _('Direction') - %th= _('Last update') - %th - %th - %tbody.js-mirrors-table-body - = render_if_exists 'projects/mirrors/table_pull_row' - - @project.remote_mirrors.each_with_index do |mirror, index| - - if mirror.enabled - %tr - %td= mirror.safe_url - %td= _('Push') - %td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never') - %td - - if mirror.last_error.present? - .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') - %td - .btn-group.mirror-actions-group{ role: 'group' } - = render 'shared/remote_mirror_update_button', remote_mirror: mirror - %button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') diff --git a/app/views/projects/mirrors/_push_pull_form.html.haml b/app/views/projects/mirrors/_push_pull_form.html.haml deleted file mode 100644 index f7203103432..00000000000 --- a/app/views/projects/mirrors/_push_pull_form.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') - -= f.fields_for :remote_mirrors, @remote_mirror do |rm_f| - = rm_f.hidden_field :enabled, value: '1' - = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+" - = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden' \ No newline at end of file diff --git a/app/views/projects/mirrors/_show.html.haml b/app/views/projects/mirrors/_show.html.haml index ae5022bb243..8318d5898a1 100644 --- a/app/views/projects/mirrors/_show.html.haml +++ b/app/views/projects/mirrors/_show.html.haml @@ -1 +1 @@ -= render 'projects/mirrors/push_pull' \ No newline at end of file += render 'projects/mirrors/mirror_repos' -- cgit v1.2.1 From 493b225b973a1cac44e32da41de29a3543742a92 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 2 Aug 2018 00:17:26 +0100 Subject: Disable direction select if 1 option --- app/views/projects/mirrors/_mirror_repos.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index 92770f15335..dd6233e88a6 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -31,7 +31,7 @@ .form-group = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' - = select_tag :mirror_direction, options_for_select(options), class: 'form-control js-mirror-direction' + = select_tag :mirror_direction, options_for_select(options), class: 'form-control js-mirror-direction', disabled: "#{'disabled' if options.count == 1}" = render 'projects/mirrors/mirror_repos_form', can_push: can_push, can_pull: can_pull, f: f -- cgit v1.2.1 From 6aa33977c89bb954fe54579abd02bf719e3efbc6 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 1 Aug 2018 23:49:19 +0000 Subject: Fix mirror_helper.rb EOF line --- app/helpers/mirror_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb index dc611ea7408..93ed22513ac 100644 --- a/app/helpers/mirror_helper.rb +++ b/app/helpers/mirror_helper.rb @@ -2,4 +2,4 @@ module MirrorHelper def mirrors_form_data_attributes { project_mirror_endpoint: project_mirror_path(@project) } end -end \ No newline at end of file +end -- cgit v1.2.1 From 9b48c6886e391a8fcc950b37ae8ea374abb36a86 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 2 Aug 2018 01:01:45 +0100 Subject: port specs --- spec/features/projects/remote_mirror_spec.rb | 8 ++++++-- spec/features/projects/settings/repository_settings_spec.rb | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb index 5259a8942dc..5b91af6bb5a 100644 --- a/spec/features/projects/remote_mirror_spec.rb +++ b/spec/features/projects/remote_mirror_spec.rb @@ -17,7 +17,9 @@ describe 'Project remote mirror', :feature do visit project_mirror_path(project) - expect(page).to have_content('The remote repository failed to update.') + row = first('.js-mirrors-table-body tr') + expect(row).to have_content('Error') + expect(row).to have_content('Never') end end @@ -27,7 +29,9 @@ describe 'Project remote mirror', :feature do visit project_mirror_path(project) - expect(page).to have_content('The remote repository failed to update 5 minutes ago.') + row = first('.js-mirrors-table-body tr') + expect(row).to have_content('Error') + expect(row).to have_content('5 minutes ago') end end end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index a0f5b234ebc..377a75cbcb3 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -129,9 +129,8 @@ describe 'Projects > Settings > Repository settings' do visit project_settings_repository_path(project) end - it 'shows push mirror settings' do - expect(page).to have_selector('#project_remote_mirrors_attributes_0_enabled') - expect(page).to have_selector('#project_remote_mirrors_attributes_0_url') + it 'shows push mirror settings', :js do + expect(page).to have_selector('#mirror_direction') end end end -- cgit v1.2.1 From 8fa270bc605ce08970a132a96907e18941bf04e1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 2 Aug 2018 00:34:00 +0000 Subject: Fix settings.scss --- app/assets/stylesheets/pages/settings.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index fc5212cfade..fb03970f64f 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -308,10 +308,6 @@ color: $white-light; } -.push-pull-table { - margin-top: 1em; -} - .push-pull-table { margin-top: 1em; -- cgit v1.2.1 From b2363b9418054af8f81dcf31e3624ac3665f12c9 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 2 Aug 2018 01:38:30 +0100 Subject: regenerate gitlab.pot --- locale/gitlab.pot | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 09a35b5da07..20cb3644529 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,6 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-07-10 16:02-0700\n" -"PO-Revision-Date: 2018-07-10 16:02-0700\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -597,6 +595,9 @@ msgstr "" msgid "Authentication Log" msgstr "" +msgid "Authentication method" +msgstr "" + msgid "Author" msgstr "" @@ -2102,6 +2103,9 @@ msgstr "" msgid "Diffs|Something went wrong while fetching diff lines." msgstr "" +msgid "Direction" +msgstr "" + msgid "Directory name" msgstr "" @@ -2321,6 +2325,9 @@ msgstr "" msgid "Environments|You don't have any environments right now." msgstr "" +msgid "Error" +msgstr "" + msgid "Error Reporting and Logging" msgstr "" @@ -2435,6 +2442,9 @@ msgstr "" msgid "Failed to remove issue from board, please try again." msgstr "" +msgid "Failed to remove mirror." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "" @@ -2919,6 +2929,9 @@ msgstr "" msgid "Inline" msgstr "" +msgid "Input your repository URL" +msgstr "" + msgid "Install GitLab Runner" msgstr "" @@ -3323,6 +3336,21 @@ msgstr "" msgid "Milestones|Promote Milestone" msgstr "" +msgid "Mirror a repository" +msgstr "" + +msgid "Mirror direction" +msgstr "" + +msgid "Mirror repository" +msgstr "" + +msgid "Mirrored repositories" +msgstr "" + +msgid "Mirroring repositories" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" @@ -3377,6 +3405,9 @@ msgstr "" msgid "Nav|Sign out and sign in with a different account" msgstr "" +msgid "Never" +msgstr "" + msgid "New" msgstr "" @@ -3622,6 +3653,9 @@ msgstr "" msgid "Only comments from the following commit are shown below" msgstr "" +msgid "Only mirror protected branches" +msgstr "" + msgid "Only project members can comment." msgstr "" @@ -4183,6 +4217,12 @@ msgstr "" msgid "Public pipelines" msgstr "" +msgid "Pull" +msgstr "" + +msgid "Push" +msgstr "" + msgid "Push events" msgstr "" @@ -4515,6 +4555,9 @@ msgstr "" msgid "Set up Koding" msgstr "" +msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically." +msgstr "" + msgid "SetPasswordToCloneLink|set a password" msgstr "" @@ -4903,6 +4946,9 @@ msgstr "" msgid "Test coverage parsing" msgstr "" +msgid "The Git LFS objects will not be synced." +msgstr "" + msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" msgstr "" @@ -4960,6 +5006,9 @@ msgstr "" msgid "The repository must be accessible over http://, https:// or git://." msgstr "" +msgid "The repository must be accessible over http://, https://, ssh:// and git://." +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "" @@ -4984,6 +5033,9 @@ msgstr "" msgid "The time taken by each data entry gathered by that stage." msgstr "" +msgid "The update action will time out after 10 minutes. For big repositories, use a clone/push combination." +msgstr "" + msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of :. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side." msgstr "" @@ -5119,6 +5171,9 @@ msgstr "" msgid "This user has no identities" msgstr "" +msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches." +msgstr "" + msgid "Time before an issue gets scheduled" msgstr "" @@ -5425,9 +5480,15 @@ msgid_plural "Update %{files} files" msgstr[0] "" msgstr[1] "" +msgid "Update now" +msgstr "" + msgid "Update your group name, description, avatar, and other general settings." msgstr "" +msgid "Updating" +msgstr "" + msgid "Upload GoogleCodeProjectHosting.json here:" msgstr "" -- cgit v1.2.1 From 5f1dfa9a22b11d72067bdcf8c98cc192b514d8bb Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 2 Aug 2018 10:37:20 +0100 Subject: Fix direction select disabled --- app/views/projects/mirrors/_mirror_repos.html.haml | 2 +- app/views/projects/mirrors/_mirror_repos_form.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index dd6233e88a6..6334ccb22a8 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -31,7 +31,7 @@ .form-group = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' - = select_tag :mirror_direction, options_for_select(options), class: 'form-control js-mirror-direction', disabled: "#{'disabled' if options.count == 1}" + = select_tag :mirror_direction, options_for_select(options), class: 'form-control js-mirror-direction', disabled: options.count == 1 = render 'projects/mirrors/mirror_repos_form', can_push: can_push, can_pull: can_pull, f: f diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml index 3073ad67b62..2af5ed6433b 100644 --- a/app/views/projects/mirrors/_mirror_repos_form.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml @@ -7,7 +7,7 @@ .form-group = label_tag :auth_method, _('Authentication method'), class: 'label-bold' - = select_tag :auth_method, options_for_select([[_('Password'), 'password']], 'password'), { class: "form-control js-auth-method", disabled: 'disabled' } + = select_tag :auth_method, options_for_select([[_('Password'), 'password']], 'password'), { class: "form-control js-auth-method", disabled: true } .form-group = label_tag :password, _('Password'), class: 'label-bold' -- cgit v1.2.1 From a1d22274045da0cce94a81d64b3698bb092e70d1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 3 Aug 2018 13:06:15 +0100 Subject: use expect_mirror_to_have_error_and_timeago in remote_mirror_spec --- spec/features/projects/remote_mirror_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb index 5b91af6bb5a..33e9b73efe8 100644 --- a/spec/features/projects/remote_mirror_spec.rb +++ b/spec/features/projects/remote_mirror_spec.rb @@ -17,9 +17,7 @@ describe 'Project remote mirror', :feature do visit project_mirror_path(project) - row = first('.js-mirrors-table-body tr') - expect(row).to have_content('Error') - expect(row).to have_content('Never') + expect_mirror_to_have_error_and_timeago('Never') end end @@ -29,10 +27,14 @@ describe 'Project remote mirror', :feature do visit project_mirror_path(project) - row = first('.js-mirrors-table-body tr') - expect(row).to have_content('Error') - expect(row).to have_content('5 minutes ago') + expect_mirror_to_have_error_and_timeago('5 minutes ago') end end + + def expect_mirror_to_have_error_and_timeago(timeago) + row = first('.js-mirrors-table-body tr') + expect(row).to have_content('Error') + expect(row).to have_content(timeago) + end end end -- cgit v1.2.1 From 693b5ea80973abdea2fad543451652d65c4f966f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 3 Aug 2018 13:07:40 +0100 Subject: Make Pull default direction --- app/views/projects/mirrors/_mirror_repos.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index 6334ccb22a8..5f61d059d68 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -3,8 +3,8 @@ - can_push = can?(current_user, :admin_remote_mirror, @project) - can_pull = can?(current_user, :admin_mirror, @project) - options = [] -- options.unshift([_('Pull'), 'pull']) if can_pull -- options.unshift([_('Push'), 'push']) if can_push +- options.push([_('Pull'), 'pull']) if can_pull +- options.push([_('Push'), 'push']) if can_push %section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) } .settings-header -- cgit v1.2.1 From afca4f6a457c1a9bb6e2857b99c6f9b13f61af6e Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 3 Aug 2018 13:24:02 +0100 Subject: Move direction select to mirror_repos_form --- app/views/projects/mirrors/_mirror_repos.html.haml | 11 +---------- app/views/projects/mirrors/_mirror_repos_form.html.haml | 4 ++++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index 5f61d059d68..53387b3a50c 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -1,10 +1,5 @@ - expanded = Rails.env.test? - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') -- can_push = can?(current_user, :admin_remote_mirror, @project) -- can_pull = can?(current_user, :admin_mirror, @project) -- options = [] -- options.push([_('Pull'), 'pull']) if can_pull -- options.push([_('Push'), 'push']) if can_push %section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) } .settings-header @@ -29,11 +24,7 @@ = render 'projects/mirrors/instructions' - .form-group - = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' - = select_tag :mirror_direction, options_for_select(options), class: 'form-control js-mirror-direction', disabled: options.count == 1 - - = render 'projects/mirrors/mirror_repos_form', can_push: can_push, can_pull: can_pull, f: f + = render 'projects/mirrors/mirror_repos_form', f: f .form-check.append-bottom-10 = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input' diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml index 2af5ed6433b..6641376b677 100644 --- a/app/views/projects/mirrors/_mirror_repos_form.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml @@ -1,5 +1,9 @@ - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') +.form-group + = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' + = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true + = f.fields_for :remote_mirrors, @remote_mirror do |rm_f| = rm_f.hidden_field :enabled, value: '1' = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+" -- cgit v1.2.1 From 4457d98ed240555a3df143d9d11582aa3c4c893a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 3 Aug 2018 20:34:14 +0100 Subject: Review changes --- .../settings/repository/show/mirror_repos.js | 23 +++++++++++++++++----- .../projects/mirrors/_mirror_repos_form.html.haml | 4 ++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js index 91070b3c6f2..76756f76642 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js @@ -15,10 +15,17 @@ export default class MirrorRepos { } init() { - this.registerUpdateListeners(); this.initMirrorPush(); + this.registerUpdateListeners(); + } - this.$table.on('click', '.js-delete-mirror', this.deleteMirror.bind(this)); + initMirrorPush() { + this.$passwordGroup = $('.js-password-group', this.$container); + this.$password = $('.js-password', this.$passwordGroup); + this.$authMethod = $('.js-auth-method', this.$form); + + this.$authMethod.on('change', () => this.togglePassword()); + this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl()); } updateUrl() { @@ -43,11 +50,17 @@ export default class MirrorRepos { this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200); this.$urlInput.on('input', () => this.debouncedUpdateUrl()); this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches()); + this.$table.on('click', '.js-delete-mirror', this.deleteMirror.bind(this)); } - initMirrorPush() { - this.$password = $('.js-password', this.$form); - this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl()); + togglePassword() { + const isPassword = this.$authMethod.val() === 'password'; + + if (!isPassword) { + this.$password.val(''); + this.updateUrl(); + } + this.$passwordGroup.collapse(isPassword ? 'show' : 'hide'); } deleteMirror(event, existingPayload) { diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml index 6641376b677..7927e459123 100644 --- a/app/views/projects/mirrors/_mirror_repos_form.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml @@ -11,8 +11,8 @@ .form-group = label_tag :auth_method, _('Authentication method'), class: 'label-bold' - = select_tag :auth_method, options_for_select([[_('Password'), 'password']], 'password'), { class: "form-control js-auth-method", disabled: true } + = select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method", disabled: true } -.form-group +.form-group.js-password-group.collapse = label_tag :password, _('Password'), class: 'label-bold' = text_field_tag :password, '', class: 'form-control js-password' -- cgit v1.2.1 From b2543977ff479e120746a9d826956b7a2cb6f463 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 3 Aug 2018 22:31:41 +0100 Subject: regenerate gitlab.pot --- locale/gitlab.pot | 3 --- 1 file changed, 3 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 20cb3644529..8f5b04762d2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4217,9 +4217,6 @@ msgstr "" msgid "Public pipelines" msgstr "" -msgid "Pull" -msgstr "" - msgid "Push" msgstr "" -- cgit v1.2.1 From 9d5d5d86482d7e349dad6f0269d35577928b3c1b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 6 Aug 2018 11:29:52 +0100 Subject: regenerate gitlab.pot --- locale/gitlab.pot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9b05dabdade..537824d530c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -634,6 +634,9 @@ msgstr "" msgid "Authentication Log" msgstr "" +msgid "Authentication log" +msgstr "" + msgid "Authentication method" msgstr "" @@ -3507,6 +3510,9 @@ msgstr "" msgid "Nav|Sign out and sign in with a different account" msgstr "" +msgid "Network" +msgstr "" + msgid "Never" msgstr "" -- cgit v1.2.1 From 85740de09dfe4bd314e3abcf7b165bc4ea06495b Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Mon, 6 Aug 2018 14:07:12 +0000 Subject: Add in username help text --- app/views/projects/mirrors/_instructions.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index bc422543c88..058c889781d 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -3,6 +3,7 @@ %li = _('The repository must be accessible over http://, https://, ssh:// and git://.').html_safe + %li= _('Include the username in the URL if required: https://username@gitlab.company.com/group/project.git.').html_safe %li= _('The update action will time out after 10 minutes. For big repositories, use a clone/push combination.') %li= _('The Git LFS objects will not be synced.').html_safe %li -- cgit v1.2.1 From 847c698b39b9030b6d693ad5eee31e97d520d27d Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Mon, 6 Aug 2018 16:26:52 +0200 Subject: Optimize querying User#manageable_groups --- app/models/user.rb | 19 +++++-------------- .../ce-5666-optimize_querying_manageable_groups.yml | 5 +++++ 2 files changed, 10 insertions(+), 14 deletions(-) create mode 100644 changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml diff --git a/app/models/user.rb b/app/models/user.rb index 37f2e8b680e..fb19de4b980 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -101,6 +101,10 @@ class User < ActiveRecord::Base has_many :groups, through: :group_members has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group + has_many :owned_or_maintainers_groups, + -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) }, + through: :group_members, + source: :group alias_attribute :masters_groups, :maintainers_groups # Projects @@ -982,15 +986,7 @@ class User < ActiveRecord::Base end def manageable_groups - union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), maintainers_groups.select(:id)]).to_sql - - # Update this line to not use raw SQL when migrated to Rails 5.2. - # Either ActiveRecord or Arel constructions are fine. - # This was replaced with the raw SQL construction because of bugs in the arel gem. - # Bugs were fixed in arel 9.0.0 (Rails 5.2). - owned_and_maintainer_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection - - Gitlab::GroupHierarchy.new(owned_and_maintainer_groups).base_and_descendants + Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants end def namespaces @@ -1244,11 +1240,6 @@ class User < ActiveRecord::Base !terms_accepted? end - def owned_or_maintainers_groups - union = Gitlab::SQL::Union.new([owned_groups, maintainers_groups]) - Group.from("(#{union.to_sql}) namespaces") - end - # @deprecated alias_method :owned_or_masters_groups, :owned_or_maintainers_groups diff --git a/changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml b/changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml new file mode 100644 index 00000000000..0c6a1071cdd --- /dev/null +++ b/changelogs/unreleased/ce-5666-optimize_querying_manageable_groups.yml @@ -0,0 +1,5 @@ +--- +title: Optimize querying User#manageable_groups +merge_request: 21050 +author: +type: performance -- cgit v1.2.1 From 8403b38940205ab3f3cf14bda78f641c99e112bd Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 7 Aug 2018 15:10:25 +0100 Subject: port changes --- .../pages/projects/settings/repository/show/mirror_repos.js | 2 +- app/assets/stylesheets/framework/buttons.scss | 4 ---- app/views/projects/mirrors/_instructions.html.haml | 3 ++- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js index 76756f76642..4c56af20cc3 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js @@ -50,7 +50,7 @@ export default class MirrorRepos { this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200); this.$urlInput.on('input', () => this.debouncedUpdateUrl()); this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches()); - this.$table.on('click', '.js-delete-mirror', this.deleteMirror.bind(this)); + this.$table.on('click', '.js-delete-mirror', event => this.deleteMirror(event)); } togglePassword() { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index a842ff92f8e..646cedd79ed 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -242,10 +242,6 @@ &:not(:last-child) { margin-right: 5px; } - - &.hide { - display: none; - } } } diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index bc422543c88..3d811be3fe3 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -3,7 +3,8 @@ %li = _('The repository must be accessible over http://, https://, ssh:// and git://.').html_safe - %li= _('The update action will time out after 10 minutes. For big repositories, use a clone/push combination.') + %li= _('Include the username in the URL if required: https://username@gitlab.company.com/group/project.git.').html_safe + %li= _('The update action will time out after 15 minutes. For big repositories, use a clone/push combination.') %li= _('The Git LFS objects will not be synced.').html_safe %li = _('This user will be the author of all events in the activity feed that are the result of an update, -- cgit v1.2.1 From bcdc6f31195f176c6256c2960e6b87e56b62b522 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 7 Aug 2018 16:06:38 +0100 Subject: Moves report components to reports folder --- .../components/grouped_test_reports_app.vue | 8 +- .../javascripts/reports/components/issue_body.js | 9 + .../reports/components/issue_status_icon.vue | 57 ++++++ .../javascripts/reports/components/issues_list.vue | 85 +++++++++ .../reports/components/modal_open_name.vue | 33 ++++ .../reports/components/report_issues.vue | 59 ++++++ .../javascripts/reports/components/report_link.vue | 29 +++ .../reports/components/report_section.vue | 181 +++++++++++++++++++ .../javascripts/reports/components/summary_row.vue | 71 ++++++++ app/assets/javascripts/reports/constants.js | 2 + .../vue_shared/components/reports/constants.js | 3 - .../vue_shared/components/reports/issue_body.js | 9 - .../components/reports/issue_status_icon.vue | 58 ------ .../vue_shared/components/reports/issues_list.vue | 85 --------- .../components/reports/modal_open_name.vue | 33 ---- .../components/reports/report_issues.vue | 59 ------ .../vue_shared/components/reports/report_link.vue | 29 --- .../components/reports/report_section.vue | 181 ------------------- .../vue_shared/components/reports/summary_row.vue | 71 -------- .../reports/components/modal_open_name_spec.js | 45 +++++ .../reports/components/report_link_spec.js | 71 ++++++++ .../reports/components/report_section_spec.js | 197 +++++++++++++++++++++ .../reports/components/summary_row_spec.js | 37 ++++ .../components/reports/modal_open_name_spec.js | 45 ----- .../components/reports/report_link_spec.js | 71 -------- .../components/reports/report_section_spec.js | 197 --------------------- .../components/reports/summary_row_spec.js | 37 ---- 27 files changed, 880 insertions(+), 882 deletions(-) create mode 100644 app/assets/javascripts/reports/components/issue_body.js create mode 100644 app/assets/javascripts/reports/components/issue_status_icon.vue create mode 100644 app/assets/javascripts/reports/components/issues_list.vue create mode 100644 app/assets/javascripts/reports/components/modal_open_name.vue create mode 100644 app/assets/javascripts/reports/components/report_issues.vue create mode 100644 app/assets/javascripts/reports/components/report_link.vue create mode 100644 app/assets/javascripts/reports/components/report_section.vue create mode 100644 app/assets/javascripts/reports/components/summary_row.vue delete mode 100644 app/assets/javascripts/vue_shared/components/reports/constants.js delete mode 100644 app/assets/javascripts/vue_shared/components/reports/issue_body.js delete mode 100644 app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue delete mode 100644 app/assets/javascripts/vue_shared/components/reports/issues_list.vue delete mode 100644 app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue delete mode 100644 app/assets/javascripts/vue_shared/components/reports/report_issues.vue delete mode 100644 app/assets/javascripts/vue_shared/components/reports/report_link.vue delete mode 100644 app/assets/javascripts/vue_shared/components/reports/report_section.vue delete mode 100644 app/assets/javascripts/vue_shared/components/reports/summary_row.vue create mode 100644 spec/javascripts/reports/components/modal_open_name_spec.js create mode 100644 spec/javascripts/reports/components/report_link_spec.js create mode 100644 spec/javascripts/reports/components/report_section_spec.js create mode 100644 spec/javascripts/reports/components/summary_row_spec.js delete mode 100644 spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js delete mode 100644 spec/javascripts/vue_shared/components/reports/report_link_spec.js delete mode 100644 spec/javascripts/vue_shared/components/reports/report_section_spec.js delete mode 100644 spec/javascripts/vue_shared/components/reports/summary_row_spec.js diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue index 140475b4dfa..7b37f4e9a97 100644 --- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue +++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue @@ -1,10 +1,10 @@ + diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue new file mode 100644 index 00000000000..dbb8848d1fa --- /dev/null +++ b/app/assets/javascripts/reports/components/issues_list.vue @@ -0,0 +1,85 @@ + + diff --git a/app/assets/javascripts/reports/components/modal_open_name.vue b/app/assets/javascripts/reports/components/modal_open_name.vue new file mode 100644 index 00000000000..4f81cee2a38 --- /dev/null +++ b/app/assets/javascripts/reports/components/modal_open_name.vue @@ -0,0 +1,33 @@ + + diff --git a/app/assets/javascripts/reports/components/report_issues.vue b/app/assets/javascripts/reports/components/report_issues.vue new file mode 100644 index 00000000000..884f55c8dec --- /dev/null +++ b/app/assets/javascripts/reports/components/report_issues.vue @@ -0,0 +1,59 @@ + + diff --git a/app/assets/javascripts/reports/components/report_link.vue b/app/assets/javascripts/reports/components/report_link.vue new file mode 100644 index 00000000000..74d68f9f439 --- /dev/null +++ b/app/assets/javascripts/reports/components/report_link.vue @@ -0,0 +1,29 @@ + + diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue new file mode 100644 index 00000000000..dc609d6f90e --- /dev/null +++ b/app/assets/javascripts/reports/components/report_section.vue @@ -0,0 +1,181 @@ + + diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue new file mode 100644 index 00000000000..4456d84c968 --- /dev/null +++ b/app/assets/javascripts/reports/components/summary_row.vue @@ -0,0 +1,71 @@ + + diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js index 807ecb1039e..c323dc543f3 100644 --- a/app/assets/javascripts/reports/constants.js +++ b/app/assets/javascripts/reports/constants.js @@ -11,6 +11,8 @@ export const SUCCESS = 'SUCCESS'; export const STATUS_FAILED = 'failed'; export const STATUS_SUCCESS = 'success'; +export const STATUS_NEUTRAL = 'neutral'; + export const ICON_WARNING = 'warning'; export const ICON_SUCCESS = 'success'; export const ICON_NOTFOUND = 'notfound'; diff --git a/app/assets/javascripts/vue_shared/components/reports/constants.js b/app/assets/javascripts/vue_shared/components/reports/constants.js deleted file mode 100644 index dbde648bfdb..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/constants.js +++ /dev/null @@ -1,3 +0,0 @@ -export const STATUS_FAILED = 'failed'; -export const STATUS_SUCCESS = 'success'; -export const STATUS_NEUTRAL = 'neutral'; diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_body.js b/app/assets/javascripts/vue_shared/components/reports/issue_body.js deleted file mode 100644 index 54dfb7b16bf..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/issue_body.js +++ /dev/null @@ -1,9 +0,0 @@ -import TestIssueBody from '~/reports/components/test_issue_body.vue'; - -export const components = { - TestIssueBody, -}; - -export const componentNames = { - TestIssueBody: TestIssueBody.name, -}; diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue b/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue deleted file mode 100644 index f8189117ac3..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue +++ /dev/null @@ -1,58 +0,0 @@ - - diff --git a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue b/app/assets/javascripts/vue_shared/components/reports/issues_list.vue deleted file mode 100644 index 2545e84f932..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue +++ /dev/null @@ -1,85 +0,0 @@ - - diff --git a/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue b/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue deleted file mode 100644 index 4f81cee2a38..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue +++ /dev/null @@ -1,33 +0,0 @@ - - diff --git a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue b/app/assets/javascripts/vue_shared/components/reports/report_issues.vue deleted file mode 100644 index 1f13e555b31..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue +++ /dev/null @@ -1,59 +0,0 @@ - - diff --git a/app/assets/javascripts/vue_shared/components/reports/report_link.vue b/app/assets/javascripts/vue_shared/components/reports/report_link.vue deleted file mode 100644 index 74d68f9f439..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/report_link.vue +++ /dev/null @@ -1,29 +0,0 @@ - - diff --git a/app/assets/javascripts/vue_shared/components/reports/report_section.vue b/app/assets/javascripts/vue_shared/components/reports/report_section.vue deleted file mode 100644 index a6dbf21092b..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/report_section.vue +++ /dev/null @@ -1,181 +0,0 @@ - - diff --git a/app/assets/javascripts/vue_shared/components/reports/summary_row.vue b/app/assets/javascripts/vue_shared/components/reports/summary_row.vue deleted file mode 100644 index 063beab58fc..00000000000 --- a/app/assets/javascripts/vue_shared/components/reports/summary_row.vue +++ /dev/null @@ -1,71 +0,0 @@ - - diff --git a/spec/javascripts/reports/components/modal_open_name_spec.js b/spec/javascripts/reports/components/modal_open_name_spec.js new file mode 100644 index 00000000000..b18b3ef03d1 --- /dev/null +++ b/spec/javascripts/reports/components/modal_open_name_spec.js @@ -0,0 +1,45 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import component from '~/reports/components/modal_open_name.vue'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; + +describe('Modal open name', () => { + const Component = Vue.extend(component); + let vm; + + const store = new Vuex.Store({ + actions: { + openModal: () => {}, + }, + state: {}, + mutations: {}, + }); + + beforeEach(() => { + vm = mountComponentWithStore(Component, { + store, + props: { + issue: { + title: 'Issue', + }, + status: 'failed', + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders the issue name', () => { + expect(vm.$el.textContent.trim()).toEqual('Issue'); + }); + + it('calls openModal actions when button is clicked', () => { + spyOn(vm, 'openModal'); + + vm.$el.click(); + + expect(vm.openModal).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/reports/components/report_link_spec.js b/spec/javascripts/reports/components/report_link_spec.js new file mode 100644 index 00000000000..cd6911e2f59 --- /dev/null +++ b/spec/javascripts/reports/components/report_link_spec.js @@ -0,0 +1,71 @@ +import Vue from 'vue'; +import component from '~/reports/components/report_link.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('report link', () => { + let vm; + + const Component = Vue.extend(component); + + afterEach(() => { + vm.$destroy(); + }); + + describe('With url', () => { + it('renders link', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: '/Gemfile.lock', + }, + }); + + expect(vm.$el.textContent.trim()).toContain('in'); + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('/Gemfile.lock'); + expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Gemfile.lock'); + }); + }); + + describe('Without url', () => { + it('does not render link', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + }, + }); + + expect(vm.$el.querySelector('a')).toBeNull(); + expect(vm.$el.textContent.trim()).toContain('in'); + expect(vm.$el.textContent.trim()).toContain('Gemfile.lock'); + }); + }); + + describe('with line', () => { + it('renders line number', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: + 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', + line: 22, + }, + }); + + expect(vm.$el.querySelector('a').textContent.trim()).toContain('Gemfile.lock:22'); + }); + }); + + describe('without line', () => { + it('does not render line number', () => { + vm = mountComponent(Component, { + issue: { + path: 'Gemfile.lock', + urlPath: + 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', + }, + }); + + expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(':22'); + }); + }); +}); diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js new file mode 100644 index 00000000000..6f6eb161d14 --- /dev/null +++ b/spec/javascripts/reports/components/report_section_spec.js @@ -0,0 +1,197 @@ +import Vue from 'vue'; +import reportSection from '~/reports/components/report_section.vue'; +import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper'; + +describe('Report section', () => { + let vm; + const ReportSection = Vue.extend(reportSection); + + const resolvedIssues = [ + { + name: 'Insecure Dependency', + fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5', + path: 'Gemfile.lock', + line: 12, + urlPath: 'foo/Gemfile.lock', + }, + ]; + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + beforeEach(() => { + vm = mountComponent(ReportSection, { + component: '', + status: 'SUCCESS', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + resolvedIssues, + hasIssues: false, + alwaysOpen: false, + }); + }); + + describe('isCollapsible', () => { + const testMatrix = [ + { hasIssues: false, alwaysOpen: false, isCollapsible: false }, + { hasIssues: false, alwaysOpen: true, isCollapsible: false }, + { hasIssues: true, alwaysOpen: false, isCollapsible: true }, + { hasIssues: true, alwaysOpen: true, isCollapsible: false }, + ]; + + testMatrix.forEach(({ hasIssues, alwaysOpen, isCollapsible }) => { + const issues = hasIssues ? 'has issues' : 'has no issues'; + const open = alwaysOpen ? 'is always open' : 'is not always open'; + + it(`is ${isCollapsible}, if the report ${issues} and ${open}`, done => { + vm.hasIssues = hasIssues; + vm.alwaysOpen = alwaysOpen; + + Vue.nextTick() + .then(() => { + expect(vm.isCollapsible).toBe(isCollapsible); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('isExpanded', () => { + const testMatrix = [ + { isCollapsed: false, alwaysOpen: false, isExpanded: true }, + { isCollapsed: false, alwaysOpen: true, isExpanded: true }, + { isCollapsed: true, alwaysOpen: false, isExpanded: false }, + { isCollapsed: true, alwaysOpen: true, isExpanded: true }, + ]; + + testMatrix.forEach(({ isCollapsed, alwaysOpen, isExpanded }) => { + const issues = isCollapsed ? 'is collapsed' : 'is not collapsed'; + const open = alwaysOpen ? 'is always open' : 'is not always open'; + + it(`is ${isExpanded}, if the report ${issues} and ${open}`, done => { + vm.isCollapsed = isCollapsed; + vm.alwaysOpen = alwaysOpen; + + Vue.nextTick() + .then(() => { + expect(vm.isExpanded).toBe(isExpanded); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + }); + describe('when it is loading', () => { + it('should render loading indicator', () => { + vm = mountComponent(ReportSection, { + component: '', + status: 'LOADING', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + hasIssues: false, + }); + expect(vm.$el.textContent.trim()).toEqual('Loading codeclimate report'); + }); + }); + + describe('with success status', () => { + beforeEach(() => { + vm = mountComponent(ReportSection, { + component: '', + status: 'SUCCESS', + loadingText: 'Loading codeclimate report', + errorText: 'foo', + successText: 'Code quality improved on 1 point and degraded on 1 point', + resolvedIssues, + hasIssues: true, + }); + }); + + it('should render provided data', () => { + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Code quality improved on 1 point and degraded on 1 point', + ); + + expect(vm.$el.querySelectorAll('.js-mr-code-resolved-issues li').length).toEqual( + resolvedIssues.length, + ); + }); + + describe('toggleCollapsed', () => { + const hiddenCss = { display: 'none' }; + + it('toggles issues', done => { + vm.$el.querySelector('button').click(); + + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse'); + + vm.$el.querySelector('button').click(); + }) + .then(Vue.nextTick) + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand'); + }) + .then(done) + .catch(done.fail); + }); + + it('is always expanded, if always-open is set to true', done => { + vm.alwaysOpen = true; + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('with failed request', () => { + it('should render error indicator', () => { + vm = mountComponent(ReportSection, { + component: '', + status: 'ERROR', + loadingText: 'Loading codeclimate report', + errorText: 'Failed to load codeclimate report', + successText: 'Code quality improved on 1 point and degraded on 1 point', + hasIssues: false, + }); + expect(vm.$el.textContent.trim()).toEqual('Failed to load codeclimate report'); + }); + }); + + describe('with action buttons passed to the slot', () => { + beforeEach(() => { + vm = mountComponentWithSlots(ReportSection, { + props: { + status: 'SUCCESS', + successText: 'success', + hasIssues: true, + }, + slots: { + actionButtons: ['Action!'], + }, + }); + }); + + it('should render the passed button', () => { + expect(vm.$el.textContent.trim()).toContain('Action!'); + }); + + it('should still render the expand/collapse button', () => { + expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); + }); + }); +}); diff --git a/spec/javascripts/reports/components/summary_row_spec.js b/spec/javascripts/reports/components/summary_row_spec.js new file mode 100644 index 00000000000..fab7693581c --- /dev/null +++ b/spec/javascripts/reports/components/summary_row_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import component from '~/reports/components/summary_row.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Summary row', () => { + const Component = Vue.extend(component); + let vm; + + const props = { + summary: 'SAST detected 1 new vulnerability and 1 fixed vulnerability', + popoverOptions: { + title: 'Static Application Security Testing (SAST)', + content: 'Learn more about SAST', + }, + statusIcon: 'warning', + }; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders provided summary', () => { + expect( + vm.$el.querySelector('.report-block-list-issue-description-text').textContent.trim(), + ).toEqual(props.summary); + }); + + it('renders provided icon', () => { + expect(vm.$el.querySelector('.report-block-list-icon span').classList).toContain( + 'js-ci-status-icon-warning', + ); + }); +}); diff --git a/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js b/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js deleted file mode 100644 index 8635203c413..00000000000 --- a/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import component from '~/vue_shared/components/reports/modal_open_name.vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; - -describe('Modal open name', () => { - const Component = Vue.extend(component); - let vm; - - const store = new Vuex.Store({ - actions: { - openModal: () => {}, - }, - state: {}, - mutations: {}, - }); - - beforeEach(() => { - vm = mountComponentWithStore(Component, { - store, - props: { - issue: { - title: 'Issue', - }, - status: 'failed', - }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders the issue name', () => { - expect(vm.$el.textContent.trim()).toEqual('Issue'); - }); - - it('calls openModal actions when button is clicked', () => { - spyOn(vm, 'openModal'); - - vm.$el.click(); - - expect(vm.openModal).toHaveBeenCalled(); - }); -}); diff --git a/spec/javascripts/vue_shared/components/reports/report_link_spec.js b/spec/javascripts/vue_shared/components/reports/report_link_spec.js deleted file mode 100644 index a4691f3712f..00000000000 --- a/spec/javascripts/vue_shared/components/reports/report_link_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import Vue from 'vue'; -import component from '~/vue_shared/components/reports/report_link.vue'; -import mountComponent from '../../../helpers/vue_mount_component_helper'; - -describe('report link', () => { - let vm; - - const Component = Vue.extend(component); - - afterEach(() => { - vm.$destroy(); - }); - - describe('With url', () => { - it('renders link', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - urlPath: '/Gemfile.lock', - }, - }); - - expect(vm.$el.textContent.trim()).toContain('in'); - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('/Gemfile.lock'); - expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Gemfile.lock'); - }); - }); - - describe('Without url', () => { - it('does not render link', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - }, - }); - - expect(vm.$el.querySelector('a')).toBeNull(); - expect(vm.$el.textContent.trim()).toContain('in'); - expect(vm.$el.textContent.trim()).toContain('Gemfile.lock'); - }); - }); - - describe('with line', () => { - it('renders line number', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - urlPath: - 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', - line: 22, - }, - }); - - expect(vm.$el.querySelector('a').textContent.trim()).toContain('Gemfile.lock:22'); - }); - }); - - describe('without line', () => { - it('does not render line number', () => { - vm = mountComponent(Component, { - issue: { - path: 'Gemfile.lock', - urlPath: - 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', - }, - }); - - expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(':22'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/reports/report_section_spec.js b/spec/javascripts/vue_shared/components/reports/report_section_spec.js deleted file mode 100644 index 4e3986acb16..00000000000 --- a/spec/javascripts/vue_shared/components/reports/report_section_spec.js +++ /dev/null @@ -1,197 +0,0 @@ -import Vue from 'vue'; -import reportSection from '~/vue_shared/components/reports/report_section.vue'; -import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper'; - -describe('Report section', () => { - let vm; - const ReportSection = Vue.extend(reportSection); - - const resolvedIssues = [ - { - name: 'Insecure Dependency', - fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5', - path: 'Gemfile.lock', - line: 12, - urlPath: 'foo/Gemfile.lock', - }, - ]; - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - beforeEach(() => { - vm = mountComponent(ReportSection, { - component: '', - status: 'SUCCESS', - loadingText: 'Loading codeclimate report', - errorText: 'foo', - successText: 'Code quality improved on 1 point and degraded on 1 point', - resolvedIssues, - hasIssues: false, - alwaysOpen: false, - }); - }); - - describe('isCollapsible', () => { - const testMatrix = [ - { hasIssues: false, alwaysOpen: false, isCollapsible: false }, - { hasIssues: false, alwaysOpen: true, isCollapsible: false }, - { hasIssues: true, alwaysOpen: false, isCollapsible: true }, - { hasIssues: true, alwaysOpen: true, isCollapsible: false }, - ]; - - testMatrix.forEach(({ hasIssues, alwaysOpen, isCollapsible }) => { - const issues = hasIssues ? 'has issues' : 'has no issues'; - const open = alwaysOpen ? 'is always open' : 'is not always open'; - - it(`is ${isCollapsible}, if the report ${issues} and ${open}`, done => { - vm.hasIssues = hasIssues; - vm.alwaysOpen = alwaysOpen; - - Vue.nextTick() - .then(() => { - expect(vm.isCollapsible).toBe(isCollapsible); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('isExpanded', () => { - const testMatrix = [ - { isCollapsed: false, alwaysOpen: false, isExpanded: true }, - { isCollapsed: false, alwaysOpen: true, isExpanded: true }, - { isCollapsed: true, alwaysOpen: false, isExpanded: false }, - { isCollapsed: true, alwaysOpen: true, isExpanded: true }, - ]; - - testMatrix.forEach(({ isCollapsed, alwaysOpen, isExpanded }) => { - const issues = isCollapsed ? 'is collapsed' : 'is not collapsed'; - const open = alwaysOpen ? 'is always open' : 'is not always open'; - - it(`is ${isExpanded}, if the report ${issues} and ${open}`, done => { - vm.isCollapsed = isCollapsed; - vm.alwaysOpen = alwaysOpen; - - Vue.nextTick() - .then(() => { - expect(vm.isExpanded).toBe(isExpanded); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - }); - describe('when it is loading', () => { - it('should render loading indicator', () => { - vm = mountComponent(ReportSection, { - component: '', - status: 'LOADING', - loadingText: 'Loading codeclimate report', - errorText: 'foo', - successText: 'Code quality improved on 1 point and degraded on 1 point', - hasIssues: false, - }); - expect(vm.$el.textContent.trim()).toEqual('Loading codeclimate report'); - }); - }); - - describe('with success status', () => { - beforeEach(() => { - vm = mountComponent(ReportSection, { - component: '', - status: 'SUCCESS', - loadingText: 'Loading codeclimate report', - errorText: 'foo', - successText: 'Code quality improved on 1 point and degraded on 1 point', - resolvedIssues, - hasIssues: true, - }); - }); - - it('should render provided data', () => { - expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( - 'Code quality improved on 1 point and degraded on 1 point', - ); - - expect(vm.$el.querySelectorAll('.js-mr-code-resolved-issues li').length).toEqual( - resolvedIssues.length, - ); - }); - - describe('toggleCollapsed', () => { - const hiddenCss = { display: 'none' }; - - it('toggles issues', done => { - vm.$el.querySelector('button').click(); - - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse'); - - vm.$el.querySelector('button').click(); - }) - .then(Vue.nextTick) - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand'); - }) - .then(done) - .catch(done.fail); - }); - - it('is always expanded, if always-open is set to true', done => { - vm.alwaysOpen = true; - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('with failed request', () => { - it('should render error indicator', () => { - vm = mountComponent(ReportSection, { - component: '', - status: 'ERROR', - loadingText: 'Loading codeclimate report', - errorText: 'Failed to load codeclimate report', - successText: 'Code quality improved on 1 point and degraded on 1 point', - hasIssues: false, - }); - expect(vm.$el.textContent.trim()).toEqual('Failed to load codeclimate report'); - }); - }); - - describe('with action buttons passed to the slot', () => { - beforeEach(() => { - vm = mountComponentWithSlots(ReportSection, { - props: { - status: 'SUCCESS', - successText: 'success', - hasIssues: true, - }, - slots: { - actionButtons: ['Action!'], - }, - }); - }); - - it('should render the passed button', () => { - expect(vm.$el.textContent.trim()).toContain('Action!'); - }); - - it('should still render the expand/collapse button', () => { - expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/reports/summary_row_spec.js b/spec/javascripts/vue_shared/components/reports/summary_row_spec.js deleted file mode 100644 index ac076f05bc0..00000000000 --- a/spec/javascripts/vue_shared/components/reports/summary_row_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import Vue from 'vue'; -import component from '~/vue_shared/components/reports/summary_row.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('Summary row', () => { - const Component = Vue.extend(component); - let vm; - - const props = { - summary: 'SAST detected 1 new vulnerability and 1 fixed vulnerability', - popoverOptions: { - title: 'Static Application Security Testing (SAST)', - content: 'Learn more about SAST', - }, - statusIcon: 'warning', - }; - - beforeEach(() => { - vm = mountComponent(Component, props); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders provided summary', () => { - expect( - vm.$el.querySelector('.report-block-list-issue-description-text').textContent.trim(), - ).toEqual(props.summary); - }); - - it('renders provided icon', () => { - expect(vm.$el.querySelector('.report-block-list-icon span').classList).toContain( - 'js-ci-status-icon-warning', - ); - }); -}); -- cgit v1.2.1 From c22bff8217d8f4c5de6a5f1b88abbaabb5d89c64 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 7 Aug 2018 16:22:04 +0100 Subject: regenerate .pot --- locale/gitlab.pot | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 537824d530c..ef6b0ac639c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3007,6 +3007,9 @@ msgstr "" msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept." msgstr "" +msgid "Include the username in the URL if required: https://username@gitlab.company.com/group/project.git." +msgstr "" + msgid "Incompatible Project" msgstr "" @@ -5228,7 +5231,7 @@ msgstr "" msgid "The time taken by each data entry gathered by that stage." msgstr "" -msgid "The update action will time out after 10 minutes. For big repositories, use a clone/push combination." +msgid "The update action will time out after 15 minutes. For big repositories, use a clone/push combination." msgstr "" msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of :. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side." -- cgit v1.2.1 From 559212b1341e3a80b80de057e1a29d21fa001aba Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Fri, 3 Aug 2018 17:03:25 +0200 Subject: First port of the performance improvements --- app/assets/javascripts/diffs/components/diff_line_gutter_content.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue index 8ad1ea34245..ae655dac525 100644 --- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue +++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue @@ -110,7 +110,7 @@ export default { return false; } - return this.showCommentButton && this.hasDiscussions; + return this.hasDiscussions && this.showCommentButton; }, }, methods: { -- cgit v1.2.1 From b4eb244c3243dfe429f1d94f228bae4829fa254c Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 08:43:00 +0200 Subject: Button with Icon only rendered when hovered, deferred lazy Image checking + ImageViewer + Tooltip --- app/assets/javascripts/vue_shared/directives/tooltip.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js index 4f2412ce520..8d6e4a33e58 100644 --- a/app/assets/javascripts/vue_shared/directives/tooltip.js +++ b/app/assets/javascripts/vue_shared/directives/tooltip.js @@ -2,8 +2,10 @@ import $ from 'jquery'; export default { bind(el) { - $(el).tooltip({ - trigger: 'hover', + requestAnimationFrame(() => { + $(el).tooltip({ + trigger: 'hover', + }); }); }, -- cgit v1.2.1 From 5908ead620a99ccbc4c7bbbea5090b94c0af8d9a Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 09:25:36 +0200 Subject: Changed the if order to get perhaps even a little bit more out of it --- app/assets/javascripts/diffs/components/diff_line_gutter_content.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue index ae655dac525..8ad1ea34245 100644 --- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue +++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue @@ -110,7 +110,7 @@ export default { return false; } - return this.hasDiscussions && this.showCommentButton; + return this.showCommentButton && this.hasDiscussions; }, }, methods: { -- cgit v1.2.1 From f68f405c17d78c18d7e22cbf67895fa366628e50 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 09:36:24 +0200 Subject: Reset Tooltip directive Init + Fixed Karma Test --- app/assets/javascripts/vue_shared/directives/tooltip.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js index 8d6e4a33e58..4f2412ce520 100644 --- a/app/assets/javascripts/vue_shared/directives/tooltip.js +++ b/app/assets/javascripts/vue_shared/directives/tooltip.js @@ -2,10 +2,8 @@ import $ from 'jquery'; export default { bind(el) { - requestAnimationFrame(() => { - $(el).tooltip({ - trigger: 'hover', - }); + $(el).tooltip({ + trigger: 'hover', }); }, -- cgit v1.2.1 From e70d06dff88675e4e7d88ff8b798b4090dd4d0ae Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 11:14:00 +0200 Subject: Incremental Rendering of the MR --- app/assets/javascripts/diffs/components/app.vue | 13 +++++++++---- .../javascripts/diffs/components/diff_file.vue | 4 ++-- app/assets/javascripts/diffs/store/actions.js | 20 ++++++++++++++++++++ app/assets/javascripts/diffs/store/mutation_types.js | 1 + app/assets/javascripts/diffs/store/mutations.js | 20 +++++++++++++++++++- 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 7cc4e6a2c3a..51d86dc661f 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -114,11 +114,16 @@ export default { this.adjustView(); }, methods: { - ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']), + ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles', 'startRenderDiffsQueue']), fetchData() { - this.fetchDiffFiles().catch(() => { - createFlash(__('Something went wrong on our end. Please try again!')); - }); + this.fetchDiffFiles() + .then(() => { + console.log('Done'); + requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 }); + }) + .catch(() => { + createFlash(__('Something went wrong on our end. Please try again!')); + }); if (!this.isNotesFetched) { eventHub.$emit('fetchNotesData'); diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 7e7058d8d08..716ebad209f 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -121,12 +121,12 @@ export default {
{ .then(handleLocationHash); }; +export const startRenderDiffsQueue = ({ state, commit }) => { + const checkItem = () => { + const nextFile = state.diffFiles.find(file => !file.renderIt && !file.collapsed); + if (nextFile) { + requestAnimationFrame(() => { + commit(types.RENDER_FILE, nextFile); + }); + requestIdleCallback( + () => { + console.log('CALL NEXT'); + checkItem(); + }, + { timeout: 1000 }, + ); + } + }; + + checkItem(); +}; + export const setInlineDiffViewType = ({ commit }) => { commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE); diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js index 2c8e1a1466f..c999d637d50 100644 --- a/app/assets/javascripts/diffs/store/mutation_types.js +++ b/app/assets/javascripts/diffs/store/mutation_types.js @@ -8,3 +8,4 @@ export const REMOVE_COMMENT_FORM_LINE = 'REMOVE_COMMENT_FORM_LINE'; export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES'; export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS'; export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES'; +export const RENDER_FILE = 'RENDER_FILE'; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index a98b2be89a3..5e6b374747e 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -15,8 +15,26 @@ export default { }, [types.SET_DIFF_DATA](state, data) { + const diffData = convertObjectPropsToCamelCase(data, { deep: true }); + let showingLines = 0; + diffData.diffFiles.map(file => { + if (file.highlightedDiffLines) { + showingLines += file.parallelDiffLines.length; + Object.assign(file, { + renderIt: showingLines < 200, + collapsed: showingLines > 2000, + }); + } + }); + Object.assign(state, { - ...convertObjectPropsToCamelCase(data, { deep: true }), + ...diffData, + }); + }, + + [types.RENDER_FILE](state, file) { + Object.assign(file, { + renderIt: true, }); }, -- cgit v1.2.1 From 259e54402af87a41bc72a3b1035e435d302144ca Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 11:38:52 +0200 Subject: ESLint Fixes --- app/assets/javascripts/diffs/components/app.vue | 1 - app/assets/javascripts/diffs/store/actions.js | 1 - app/assets/javascripts/diffs/store/mutations.js | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 51d86dc661f..b5b05df4d34 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -118,7 +118,6 @@ export default { fetchData() { this.fetchDiffFiles() .then(() => { - console.log('Done'); requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 }); }) .catch(() => { diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 1b4e4239716..cf42166e243 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -38,7 +38,6 @@ export const startRenderDiffsQueue = ({ state, commit }) => { }); requestIdleCallback( () => { - console.log('CALL NEXT'); checkItem(); }, { timeout: 1000 }, diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 5e6b374747e..60859cfa9ea 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -17,7 +17,7 @@ export default { [types.SET_DIFF_DATA](state, data) { const diffData = convertObjectPropsToCamelCase(data, { deep: true }); let showingLines = 0; - diffData.diffFiles.map(file => { + diffData.diffFiles.forEach(file => { if (file.highlightedDiffLines) { showingLines += file.parallelDiffLines.length; Object.assign(file, { -- cgit v1.2.1 From 3e9965f767a4bef594e2e589e1cf3af8a68b9107 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 13:12:48 +0200 Subject: Fix for diff_file specs --- spec/javascripts/diffs/components/diff_file_spec.js | 10 +++++++++- spec/javascripts/diffs/mock_data/diff_file.js | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index 7a4616ec8eb..44a38f7ca82 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -22,11 +22,18 @@ describe('DiffFile', () => { expect(el.id).toEqual(fileHash); expect(el.classList.contains('diff-file')).toEqual(true); + expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0); expect(el.querySelector('.js-file-title')).toBeDefined(); expect(el.querySelector('.file-title-name').innerText.indexOf(filePath) > -1).toEqual(true); expect(el.querySelector('.js-syntax-highlight')).toBeDefined(); - expect(el.querySelectorAll('.line_content').length > 5).toEqual(true); + + expect(vm.file.renderIt).toEqual(false); + vm.file.renderIt = true; + + vm.$nextTick(() => { + expect(el.querySelectorAll('.line_content').length > 5).toEqual(true); + }); }); describe('collapsed', () => { @@ -34,6 +41,7 @@ describe('DiffFile', () => { expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(1); expect(vm.file.collapsed).toEqual(false); vm.file.collapsed = true; + vm.file.renderIt = true; vm.$nextTick(() => { expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(0); diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js index d3bf9525924..cce36ecc91f 100644 --- a/spec/javascripts/diffs/mock_data/diff_file.js +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -39,6 +39,7 @@ export default { viewPath: '/gitlab-org/gitlab-test/blob/spooky-stuff/CHANGELOG', replacedViewPath: null, collapsed: false, + renderIt: false, tooLarge: false, contextLinesPath: '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff', -- cgit v1.2.1 From 7558a36e1f726edd8287b71adb770afd7c97f636 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 13:14:57 +0200 Subject: Fix for displaying loading when collapsed --- app/assets/javascripts/diffs/components/diff_file.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 716ebad209f..eca9fc70490 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -126,7 +126,7 @@ export default { :diff-file="file" />
Date: Tue, 7 Aug 2018 13:21:26 +0200 Subject: Memory savings on diffLine Object --- app/assets/javascripts/diffs/store/mutations.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 60859cfa9ea..d8e32a09b16 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -18,6 +18,22 @@ export default { const diffData = convertObjectPropsToCamelCase(data, { deep: true }); let showingLines = 0; diffData.diffFiles.forEach(file => { + if (file.parallelDiffLines) { + file.parallelDiffLines.forEach(line => { + // eslint-disable-next-line no-param-reassign + delete line.text; + }); + } + + if (file.highlightedDiffLines) { + file.highlightedDiffLines.forEach(line => { + // eslint-disable-next-line no-param-reassign + if (line.left) delete line.left.text; + // eslint-disable-next-line no-param-reassign + if (line.right) delete line.right.text; + }); + } + if (file.highlightedDiffLines) { showingLines += file.parallelDiffLines.length; Object.assign(file, { -- cgit v1.2.1 From 64d0f1e20e9a8f673d30d706fec5c03c9723a3fe Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 7 Aug 2018 17:26:15 +0200 Subject: Fixed text removal memory saving + Fixed collapsed non text files --- app/assets/javascripts/diffs/components/diff_file.vue | 9 ++++++++- app/assets/javascripts/diffs/store/actions.js | 4 +++- app/assets/javascripts/diffs/store/mutations.js | 16 ++++++++-------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index eca9fc70490..e887a71c551 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -52,10 +52,16 @@ export default { handleToggle() { const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file; - if (collapsed && !highlightedDiffLines && !parallelDiffLines.length) { + if ( + collapsed && + !highlightedDiffLines && + parallelDiffLines !== undefined && + !parallelDiffLines.length + ) { this.handleLoadCollapsedDiff(); } else { this.file.collapsed = !this.file.collapsed; + this.file.renderIt = true; } }, handleLoadCollapsedDiff() { @@ -65,6 +71,7 @@ export default { .then(() => { this.isLoadingCollapsedDiff = false; this.file.collapsed = false; + this.file.renderIt = true; }) .catch(() => { this.isLoadingCollapsedDiff = false; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index cf42166e243..4ab6ceb249a 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -31,7 +31,9 @@ export const fetchDiffFiles = ({ state, commit }) => { export const startRenderDiffsQueue = ({ state, commit }) => { const checkItem = () => { - const nextFile = state.diffFiles.find(file => !file.renderIt && !file.collapsed); + const nextFile = state.diffFiles.find( + file => !file.renderIt && (!file.collapsed || !file.text), + ); if (nextFile) { requestAnimationFrame(() => { commit(types.RENDER_FILE, nextFile); diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index d8e32a09b16..e5c143696e8 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -21,26 +21,26 @@ export default { if (file.parallelDiffLines) { file.parallelDiffLines.forEach(line => { // eslint-disable-next-line no-param-reassign - delete line.text; + if (line.left) delete line.left.text; + // eslint-disable-next-line no-param-reassign + if (line.right) delete line.right.text; }); } if (file.highlightedDiffLines) { file.highlightedDiffLines.forEach(line => { // eslint-disable-next-line no-param-reassign - if (line.left) delete line.left.text; - // eslint-disable-next-line no-param-reassign - if (line.right) delete line.right.text; + delete line.text; }); } if (file.highlightedDiffLines) { showingLines += file.parallelDiffLines.length; - Object.assign(file, { - renderIt: showingLines < 200, - collapsed: showingLines > 2000, - }); } + Object.assign(file, { + renderIt: showingLines < 200, + collapsed: file.text && showingLines > 2000, + }); }); Object.assign(state, { -- cgit v1.2.1 From 53468a77712e170b9827c81e98471d858f2a487f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Lu=C3=ADs?= Date: Tue, 7 Aug 2018 22:10:15 +0100 Subject: Fix broken spec checking for extinct element --- .../merge_request/user_sees_mr_with_deleted_source_branch_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb index c1608be402a..fd4175d5227 100644 --- a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb +++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb @@ -28,7 +28,7 @@ describe 'Merge request > User sees MR with deleted source branch', :js do click_on 'Changes' wait_for_requests - expect(page).to have_selector('.diffs.tab-pane .nothing-here-block') + expect(page).to have_selector('.diffs.tab-pane .file-holder') expect(page).to have_content('Source branch does not exist.') end end -- cgit v1.2.1 From 90becf966c503fe225b20af0213a7670cafe1b7c Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 8 Aug 2018 20:08:11 +0900 Subject: Squashed commit of the following: commit b4e71d4f5a1334a27c179d086bfea57c707de4ef Author: Shinya Maeda Date: Wed Aug 8 20:07:09 2018 +0900 Add changelog commit 50bc02ca6cfcf9c8d18d5c832a00c0f9f778d475 Author: Shinya Maeda Date: Wed Aug 8 20:05:17 2018 +0900 Fix fixture path commit 15779300f98e00277db0e66fcfd865f603b45234 Author: Shinya Maeda Date: Wed Aug 8 19:59:14 2018 +0900 Revert leftovers commit e26c0ce6b1fc490c1ad8c53fd8da5b95f8ef27ed Author: Shinya Maeda Date: Wed Aug 8 19:57:30 2018 +0900 Revert quick actions commit 9e257650ad8683c5628fd717fb21a8767bfca0fc Author: Shinya Maeda Date: Wed Aug 8 19:28:40 2018 +0900 Add changelog commit 473edcf4e60200c5ec9f6b92907d4badcf9c6a94 Author: Shinya Maeda Date: Wed Aug 8 19:27:25 2018 +0900 Fix specs commit fa2d4f76235c8aa19de9712288f5c225c47ea5f0 Author: Shinya Maeda Date: Wed Aug 8 19:20:21 2018 +0900 Fix fixture commit ee3df6595e4693c4ff11b8799aa15fc2078b7843 Author: Shinya Maeda Date: Wed Aug 8 19:14:12 2018 +0900 Clean up quick action scripts commit 2398de2711f196c2a3fdedbd52b878489e7aa01e Author: Shinya Maeda Date: Wed Aug 8 18:15:21 2018 +0900 Add quick action tasks commit b0dbc47e2c29419133c1a24ed6922a68584a3e28 Author: Shinya Maeda Date: Wed Aug 8 15:33:24 2018 +0900 Simplify fixtures commit 693a95f2edb400a3db0e6e6f3021777f849f9400 Author: Shinya Maeda Date: Tue Aug 7 13:11:07 2018 +0900 Support corrupted fixtures commit d4e44eb329193cd68c964424f5343d3863802751 Author: Shinya Maeda Date: Thu Aug 2 19:07:46 2018 +0900 bring back debaggable fixtures commit 466d3ffefac20d0f3eec350ea7231e0e403da90d Author: Shinya Maeda Date: Thu Aug 2 15:25:30 2018 +0900 Revert "Decouple fixture seeds change" This reverts commit 30626cf8f559bee49bac0ea934f766bb5ad68b2d. --- .../unreleased/fix-pipeline-fixture-seeder.yml | 5 ++ db/fixtures/development/14_pipelines.rb | 63 ++++++++++++++-------- 2 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 changelogs/unreleased/fix-pipeline-fixture-seeder.yml diff --git a/changelogs/unreleased/fix-pipeline-fixture-seeder.yml b/changelogs/unreleased/fix-pipeline-fixture-seeder.yml new file mode 100644 index 00000000000..02b83062e07 --- /dev/null +++ b/changelogs/unreleased/fix-pipeline-fixture-seeder.yml @@ -0,0 +1,5 @@ +--- +title: Fix pipeline fixture seeder +merge_request: 21088 +author: +type: fixed diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index d3a63aa2a78..5535c4a14e5 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -41,7 +41,7 @@ class Gitlab::Seeder::Pipelines when: 'manual', status: :skipped }, # notify stage - { name: 'slack', stage: 'notify', when: 'manual', status: :created }, + { name: 'slack', stage: 'notify', when: 'manual', status: :success }, ] EXTERNAL_JOBS = [ { name: 'jenkins', stage: 'test', status: :success, @@ -54,16 +54,10 @@ class Gitlab::Seeder::Pipelines def seed! pipelines.each do |pipeline| - begin - BUILDS.each { |opts| build_create!(pipeline, opts) } - EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) } - print '.' - rescue ActiveRecord::RecordInvalid - print 'F' - ensure - pipeline.update_duration - pipeline.update_status - end + BUILDS.each { |opts| build_create!(pipeline, opts) } + EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) } + pipeline.update_duration + pipeline.update_status end end @@ -87,7 +81,9 @@ class Gitlab::Seeder::Pipelines branch = merge_request.source_branch merge_request.commits.last(4).map do |commit| - create_pipeline!(project, branch, commit) + create_pipeline!(project, branch, commit).tap do |pipeline| + merge_request.update!(head_pipeline_id: pipeline.id) + end end end @@ -98,7 +94,7 @@ class Gitlab::Seeder::Pipelines def create_pipeline!(project, ref, commit) - project.pipelines.create(sha: commit.id, ref: ref, source: :push) + project.pipelines.create!(sha: commit.id, ref: ref, source: :push) end def build_create!(pipeline, opts = {}) @@ -111,24 +107,39 @@ class Gitlab::Seeder::Pipelines # block directly to `Ci::Build#create!`. setup_artifacts(build) + setup_test_reports(build) setup_build_log(build) build.project.environments. find_or_create_by(name: build.expanded_environment_name) - build.save + build.save! end end def setup_artifacts(build) - return unless %w[build test].include?(build.stage) + return unless build.stage == "build" artifacts_cache_file(artifacts_archive_path) do |file| - build.job_artifacts.build(project: build.project, file_type: :archive, file: file) + build.job_artifacts.build(project: build.project, file_type: :archive, file_format: :zip, file: file) end artifacts_cache_file(artifacts_metadata_path) do |file| - build.job_artifacts.build(project: build.project, file_type: :metadata, file: file) + build.job_artifacts.build(project: build.project, file_type: :metadata, file_format: :gzip, file: file) + end + end + + def setup_test_reports(build) + return unless build.stage == "test" && build.name == "rspec:osx" + + if build.ref == build.project.default_branch + artifacts_cache_file(test_reports_pass_path) do |file| + build.job_artifacts.build(project: build.project, file_type: :junit, file_format: :gzip, file: file) + end + else + artifacts_cache_file(test_reports_failed_path) do |file| + build.job_artifacts.build(project: build.project, file_type: :junit, file_format: :gzip, file: file) + end end end @@ -171,13 +182,21 @@ class Gitlab::Seeder::Pipelines Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz' end + def test_reports_pass_path + Rails.root + 'spec/fixtures/junit/junit_ant.xml.gz' + end + + def test_reports_failed_path + Rails.root + 'spec/fixtures/junit/junit.xml.gz' + end + def artifacts_cache_file(file_path) - cache_path = file_path.to_s.gsub('ci_', "p#{@project.id}_") + file = Tempfile.new("artifacts") + file.close - FileUtils.copy(file_path, cache_path) - File.open(cache_path) do |file| - yield file - end + FileUtils.copy(file_path, file.path) + + yield(UploadedFile.new(file.path, filename: File.basename(file_path))) end end -- cgit v1.2.1 From b6ba8cc696a45e58bde24a592a9c315a0eb16744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 3 Aug 2018 22:52:01 +0200 Subject: Add ability suppress the global "You won't be able [use] SSH" message This fixes gitlab-org/gitlab-ce#49953, as noted in the documentation this feature is intended to be used when SSH certificates are enabled. Then this warning becomes not only pointless, but also misleading. This builds on top of gitlab-org/gitlab-ce!21009 since both need to modify the same documentation, which avoids a merge conflict. See also the gitlab-org/gitlab-ce#49218 issue and associated merge request. --- app/helpers/application_settings_helper.rb | 1 + app/helpers/button_helper.rb | 6 +++++- app/helpers/projects_helper.rb | 5 ++++- app/models/application_setting.rb | 3 ++- .../application_settings/_account_and_limit.html.haml | 6 ++++++ ...9953-add-user_show_add_ssh_key_message-setting.yml | 5 +++++ ...how_add_ssh_key_message_to_application_settings.rb | 19 +++++++++++++++++++ db/schema.rb | 3 ++- doc/administration/operations/ssh_certificates.md | 17 +++++++++++++++++ doc/api/settings.md | 8 ++++++-- spec/helpers/button_helper_spec.rb | 12 ++++++++++++ 11 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml create mode 100644 db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 2bdf2c2c120..1e05f07e676 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -254,6 +254,7 @@ module ApplicationSettingsHelper :usage_ping_enabled, :instance_statistics_visibility_private, :user_default_external, + :user_show_add_ssh_key_message, :user_oauth_applications, :version_check_enabled, :web_ide_clientside_preview_enabled diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 0171a880164..7adc882bc47 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -73,7 +73,11 @@ module ButtonHelper end def ssh_clone_button(project, append_link: true) - dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") if current_user.try(:require_ssh_key?) + if Gitlab::CurrentSettings.user_show_add_ssh_key_message? && + current_user.try(:require_ssh_key?) + dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") + end + append_url = project.ssh_url_to_repo if append_link dropdown_item_with_description('SSH', dropdown_description, href: append_url) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index aaf9dff43ee..6b4079b4113 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -192,7 +192,10 @@ module ProjectsHelper end def show_no_ssh_key_message? - cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? + Gitlab::CurrentSettings.user_show_add_ssh_key_message? && + cookies[:hide_no_ssh_message].blank? && + !current_user.hide_no_ssh_key && + current_user.require_ssh_key? end def show_no_password_message? diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index bbe7811841a..c77faa4b71d 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -298,7 +298,8 @@ class ApplicationSetting < ActiveRecord::Base unique_ips_limit_time_window: 3600, usage_ping_enabled: Settings.gitlab['usage_ping_enabled'], instance_statistics_visibility_private: false, - user_default_external: false + user_default_external: false, + user_show_add_ssh_key_message: true } end diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index 7c8243a7a90..622cb11010e 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -29,5 +29,11 @@ = f.check_box :user_default_external, class: 'form-check-input' = f.label :user_default_external, class: 'form-check-label' do Newly registered users will by default be external + .form-group + = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold' + .form-check + = f.check_box :user_show_add_ssh_key_message, class: 'form-check-input' + = f.label :user_show_add_ssh_key_message, class: 'form-check-label' do + Inform users without uploaded SSH keys that they can't push over SSH until one is added = f.submit 'Save changes', class: 'btn btn-success' diff --git a/changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml b/changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml new file mode 100644 index 00000000000..82423092792 --- /dev/null +++ b/changelogs/unreleased/49953-add-user_show_add_ssh_key_message-setting.yml @@ -0,0 +1,5 @@ +--- +title: Add ability to suppress the global "You won't be able to use SSH" message +merge_request: 21027 +author: Ævar Arnfjörð Bjarmason +type: added diff --git a/db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb b/db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb new file mode 100644 index 00000000000..e3019af2cc9 --- /dev/null +++ b/db/migrate/20180808162000_add_user_show_add_ssh_key_message_to_application_settings.rb @@ -0,0 +1,19 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddUserShowAddSshKeyMessageToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default :application_settings, :user_show_add_ssh_key_message, :boolean, default: true, allow_null: false + end + + def down + remove_column :application_settings, :user_show_add_ssh_key_message + end +end diff --git a/db/schema.rb b/db/schema.rb index f1d8f4df3b7..1288a98745c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180807153545) do +ActiveRecord::Schema.define(version: 20180808162000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -170,6 +170,7 @@ ActiveRecord::Schema.define(version: 20180807153545) do t.boolean "hide_third_party_offers", default: false, null: false t.boolean "instance_statistics_visibility_private", default: false, null: false t.boolean "web_ide_clientside_preview_enabled", default: false, null: false + t.boolean "user_show_add_ssh_key_message", default: true, null: false end create_table "audit_events", force: :cascade do |t| diff --git a/doc/administration/operations/ssh_certificates.md b/doc/administration/operations/ssh_certificates.md index 8968afba01b..9edccd25ced 100644 --- a/doc/administration/operations/ssh_certificates.md +++ b/doc/administration/operations/ssh_certificates.md @@ -163,3 +163,20 @@ Such a restriction can currently be hacked in by e.g. providing a custom `AuthorizedKeysCommand` which checks if the discovered key-ID returned from `gitlab-shell-authorized-keys-check` is a deploy key or not (all non-deploy keys should be refused). + +## Disabling the global warning about users lacking SSH keys + +By default GitLab will show a "You won't be able to pull or push +project code via SSH" warning to users who have not uploaded an SSH +key to their profile. + +This is counterproductive when using SSH certificates, since users +aren't expected to upload their own keys. + +To disable this warning globally, go to "Application settings -> +Account and limit settings" and disable the "Show user add SSH key +message" setting. + +This setting was added specifically for use with SSH certificates, but +can be turned off without using them if you'd like to hide the warning +for some other reason. diff --git a/doc/api/settings.md b/doc/api/settings.md index 68fc56b1fa3..b480d62e16a 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -56,7 +56,8 @@ Example response: "enforce_terms": true, "terms": "Hello world!", "performance_bar_allowed_group_id": 42, - "instance_statistics_visibility_private": false + "instance_statistics_visibility_private": false, + "user_show_add_ssh_key_message": true } ``` @@ -161,6 +162,8 @@ PUT /application/settings | `enforce_terms` | boolean | no | Enforce application ToS to all users | | `terms` | text | yes (if `enforce_terms` is true) | Markdown content for the ToS | | `instance_statistics_visibility_private` | boolean | no | When set to `true` Instance statistics will only be available to admins | +| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push ++project code via SSH" warning shown to users with no uploaded SSH key | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal @@ -206,6 +209,7 @@ Example response: "enforce_terms": true, "terms": "Hello world!", "performance_bar_allowed_group_id": 42, - "instance_statistics_visibility_private": false + "instance_statistics_visibility_private": false, + "user_show_add_ssh_key_message": true } ``` diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index 630f3eff258..0c0a0003231 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -79,6 +79,18 @@ describe ButtonHelper do end end + context 'without an ssh key on the user and user_show_add_ssh_key_message unset' do + before do + stub_application_setting(user_show_add_ssh_key_message: false) + end + + it 'there is no warning on the dropdown description' do + description = element.search('.dropdown-menu-inner-content').first + + expect(description).to be_nil + end + end + context 'with an ssh key on the user' do before do create(:key, user: user) -- cgit v1.2.1 From c23961710bbbd2b361cd8e50f29063e568490dc6 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 8 Aug 2018 15:56:21 -0500 Subject: Fix blocked user card styles --- app/views/admin/users/show.html.haml | 4 ++-- changelogs/unreleased/50126-blocked-user-card.yml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/50126-blocked-user-card.yml diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index f730fd05176..c7a3df9349a 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -138,8 +138,8 @@ %br = link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - if @user.blocked? - .card.bg-info - .card-header + .card.border-info + .card-header.bg-info.text-white This user is blocked .card-body %p A blocked user cannot: diff --git a/changelogs/unreleased/50126-blocked-user-card.yml b/changelogs/unreleased/50126-blocked-user-card.yml new file mode 100644 index 00000000000..b270bf7a757 --- /dev/null +++ b/changelogs/unreleased/50126-blocked-user-card.yml @@ -0,0 +1,5 @@ +--- +title: Fix blocked user card style +merge_request: +author: +type: fixed -- cgit v1.2.1 From 3d2a3e5782aaff37c4b27dc9d3858031ab0c9059 Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Wed, 1 Aug 2018 12:05:37 +0200 Subject: Docs: FK constraints require an index. Closes #49789. --- doc/development/migration_style_guide.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index a211effdfa7..6f31e5b82e5 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -182,6 +182,34 @@ class MyMigration < ActiveRecord::Migration end ``` +## Adding foreign-key constraints + +When adding a foreign-key constraint to either an existing or new +column remember to also add a index on the column. + +This is _required_ if the foreign-key constraint specifies +`ON DELETE CASCADE` or `ON DELETE SET NULL` behavior. On a cascading +delete, the [corresponding record needs to be retrieved using an +index](https://www.cybertec-postgresql.com/en/postgresql-indexes-and-foreign-keys/) +(otherwise, we'd need to scan the whole table) for subsequent update or +deletion. + +Here's an example where we add a new column with a foreign key +constraint. Note it includes `index: true` to create an index for it. + +```ruby +class Migration < ActiveRecord::Migration + + def change + add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade } + end +end +``` + +When adding a foreign-key constraint to an existing column, we +have to employ `add_concurrent_foreign_key` and `add_concurrent_index` +instead of `add_reference`. + ## Adding Columns With Default Values When adding columns with default values you must use the method -- cgit v1.2.1 From e3ff3909862d81036a64f3eab02d5e3e4802f5e6 Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Wed, 1 Aug 2018 17:52:54 +0200 Subject: Add rubocop check for add_reference to require index. --- db/migrate/20160317092222_add_moved_to_to_issue.rb | 2 +- rubocop/cop/migration/add_reference.rb | 49 ++++++++++++++++++++ rubocop/rubocop.rb | 1 + spec/rubocop/cop/migration/add_reference_spec.rb | 54 ++++++++++++++++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 rubocop/cop/migration/add_reference.rb create mode 100644 spec/rubocop/cop/migration/add_reference_spec.rb diff --git a/db/migrate/20160317092222_add_moved_to_to_issue.rb b/db/migrate/20160317092222_add_moved_to_to_issue.rb index 461e7fb3a9b..2bf549d7ecd 100644 --- a/db/migrate/20160317092222_add_moved_to_to_issue.rb +++ b/db/migrate/20160317092222_add_moved_to_to_issue.rb @@ -1,5 +1,5 @@ class AddMovedToToIssue < ActiveRecord::Migration def change - add_reference :issues, :moved_to, references: :issues + add_reference :issues, :moved_to, references: :issues # rubocop:disable Migration/AddReference end end diff --git a/rubocop/cop/migration/add_reference.rb b/rubocop/cop/migration/add_reference.rb new file mode 100644 index 00000000000..4b67270c97a --- /dev/null +++ b/rubocop/cop/migration/add_reference.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if a foreign key constraint is added and require a index for it + class AddReference < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = '`add_reference` requires `index: true`' + + def on_send(node) + return unless in_migration?(node) + + name = node.children[1] + + return unless name == :add_reference + + opts = node.children.last + + add_offense(node, location: :selector) unless opts && opts.type == :hash + + index_present = false + + opts.each_node(:pair) do |pair| + index_present ||= index_enabled?(pair) + end + + add_offense(node, location: :selector) unless index_present + end + + private + + def index_enabled?(pair) + hash_key_type(pair) == :sym && hash_key_name(pair) == :index && pair.children[1].true_type? + end + + def hash_key_type(pair) + pair.children[0].type + end + + def hash_key_name(pair) + pair.children[0].children[0] + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index aa7ae601f75..a427208cdab 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -11,6 +11,7 @@ require_relative 'cop/migration/add_column' require_relative 'cop/migration/add_concurrent_foreign_key' require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_index' +require_relative 'cop/migration/add_reference' require_relative 'cop/migration/add_timestamps' require_relative 'cop/migration/datetime' require_relative 'cop/migration/hash_index' diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb new file mode 100644 index 00000000000..8f795bb561e --- /dev/null +++ b/spec/rubocop/cop/migration/add_reference_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/add_reference' + +describe RuboCop::Cop::Migration::AddReference do + include CopHelper + + let(:cop) { described_class.new } + + context 'outside of a migration' do + it 'does not register any offenses' do + expect_no_offenses(<<~RUBY) + def up + add_reference(:projects, :users) + end + RUBY + end + end + + context 'in a migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when using add_reference without index' do + expect_offense(<<~RUBY) + call do + add_reference(:projects, :users) + ^^^^^^^^^^^^^ `add_reference` requires `index: true` + end + RUBY + end + + it 'registers an offense when using add_reference index disabled' do + expect_offense(<<~RUBY) + def up + add_reference(:projects, :users, index: false) + ^^^^^^^^^^^^^ `add_reference` requires `index: true` + end + RUBY + end + + it 'does not register an offense when using add_reference with index enabled' do + expect_no_offenses(<<~RUBY) + def up + add_reference(:projects, :users, index: true) + end + RUBY + end + end +end -- cgit v1.2.1 From 670db0aa2dcf0bbd2476a871a4a8ea9f4beb1817 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 9 Aug 2018 08:57:37 +0100 Subject: Allow the Web IDE to open empty merge requests Closes #48166 --- app/assets/javascripts/ide/ide_router.js | 6 +++++- changelogs/unreleased/ide-open-empty-merge-request.yml | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/ide-open-empty-merge-request.yml diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index c6d7d218e81..82f6f981e7a 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -117,7 +117,7 @@ router.beforeEach((to, from, next) => { mergeRequestId: to.params.mrid, }) .then(mr => { - store.dispatch('updateActivityBarView', activityBarViews.review); + store.dispatch('setCurrentBranchId', mr.source_branch); store.dispatch('getBranchData', { projectId: fullProjectId, @@ -144,6 +144,10 @@ router.beforeEach((to, from, next) => { }), ) .then(mrChanges => { + if (mrChanges.changes.length) { + store.dispatch('updateActivityBarView', activityBarViews.review); + } + mrChanges.changes.forEach((change, ind) => { const changeTreeEntry = store.state.entries[change.new_path]; diff --git a/changelogs/unreleased/ide-open-empty-merge-request.yml b/changelogs/unreleased/ide-open-empty-merge-request.yml new file mode 100644 index 00000000000..09cf231c25c --- /dev/null +++ b/changelogs/unreleased/ide-open-empty-merge-request.yml @@ -0,0 +1,5 @@ +--- +title: Fix empty merge requests not opening in the Web IDE +merge_request: +author: +type: fixed -- cgit v1.2.1 From b401bfd031d79bb178db1b88889ce1c831e9a075 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 9 Aug 2018 09:33:08 +0100 Subject: removed un-used commits for currentProject & currentBranchId --- app/assets/javascripts/ide/stores/actions/file.js | 3 --- changelogs/unreleased/ide-open-empty-merge-request.yml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index 9e3f5da4676..c9795750d65 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => { commit(types.SET_FILE_ACTIVE, { path, active: true }); dispatch('scrollToTab'); - - commit(types.SET_CURRENT_PROJECT, file.projectId); - commit(types.SET_CURRENT_BRANCH, file.branchId); }; export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => { diff --git a/changelogs/unreleased/ide-open-empty-merge-request.yml b/changelogs/unreleased/ide-open-empty-merge-request.yml index 09cf231c25c..05f2de5d31c 100644 --- a/changelogs/unreleased/ide-open-empty-merge-request.yml +++ b/changelogs/unreleased/ide-open-empty-merge-request.yml @@ -1,5 +1,5 @@ --- title: Fix empty merge requests not opening in the Web IDE -merge_request: +merge_request: 21102 author: type: fixed -- cgit v1.2.1 From ced1b0cbfcae03989d46752c63fe688240e0eddb Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 8 Aug 2018 17:57:55 +0100 Subject: Renames the Terminal button and adds an icon Makes the terminal open in a new tab --- app/views/projects/jobs/_sidebar.html.haml | 5 +++-- changelogs/unreleased/25990-web-terminal-improvements.yml | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/25990-web-terminal-improvements.yml diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 759efd4e9d4..0bc3f107f61 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -3,8 +3,9 @@ .blocks-container - if can?(current_user, :create_build_terminal, @build) .block - = link_to terminal_project_job_path(@project, @build), class: 'terminal-button pull-right btn visible-md-block visible-lg-block', title: 'Terminal' do - Terminal + = link_to terminal_project_job_path(@project, @build), class: 'pull-right btn btn-primary btn-inverted visible-md-block visible-lg-block', target: '_blank' do + Debug + = icon('external-link') #js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } } diff --git a/changelogs/unreleased/25990-web-terminal-improvements.yml b/changelogs/unreleased/25990-web-terminal-improvements.yml new file mode 100644 index 00000000000..99a4a82ea66 --- /dev/null +++ b/changelogs/unreleased/25990-web-terminal-improvements.yml @@ -0,0 +1,5 @@ +--- +title: Make terminal button more visible +merge_request: +author: +type: changed -- cgit v1.2.1 From 6cd2af53c530c6ebef20d6a123ca6b5d38db94da Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 9 Aug 2018 11:07:26 +0100 Subject: Fix label item height when no desc --- app/assets/stylesheets/pages/labels.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 2b40404971c..81890ec7d33 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -80,7 +80,7 @@ margin-bottom: 5px; display: flex; justify-content: space-between; - padding: $gl-padding; + padding: $gl-padding $gl-padding 9px; border-radius: $border-radius-default; border: 1px solid $theme-gray-100; @@ -254,6 +254,7 @@ padding: 0; position: relative; top: -3px; + margin: 0; } .label-badge { -- cgit v1.2.1 From 07294d8157e78826f87b97f9c62db9d3ee973a04 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 9 Aug 2018 10:17:24 +0000 Subject: Port EE changes --- app/views/projects/mirrors/_instructions.html.haml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index 64f0fde30cf..e051f9e6331 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -1,10 +1,11 @@ .account-well.prepend-top-default.append-bottom-default %ul %li - The repository must be accessible over http://, https://, ssh:// or git://. - %li - Include the username in the URL if required: https://username@gitlab.company.com/group/project.git. - %li - The update action will time out after 10 minutes. For big repositories, use a clone/push combination. - %li - The Git LFS objects will not be synced. + = _('The repository must be accessible over http://, + https://, ssh:// and git://.').html_safe + %li= _('Include the username in the URL if required: https://username@gitlab.company.com/group/project.git.').html_safe + %li= _("The update action will time out after #{import_will_timeout_message(Gitlab.config.gitlab_shell.git_timeout)} minutes. For big repositories, use a clone/push combination.") + %li= _('The Git LFS objects will not be synced.').html_safe + %li + = _('This user will be the author of all events in the activity feed that are the result of an update, + like new branches being created or new commits being pushed to existing branches.') -- cgit v1.2.1 From 37fb9034354a9680ed5b2e13e6882d0b4b5a3240 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 9 Aug 2018 12:03:39 +0200 Subject: Fixes input alignment in user admin form with errors --- app/views/admin/users/_access_levels.html.haml | 12 +++++--- app/views/admin/users/_form.html.haml | 33 ++++++++++++++-------- ...ut-alignment-on-user-admin-form-with-errors.yml | 5 ++++ 3 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 changelogs/unreleased/49770-fixes-input-alignment-on-user-admin-form-with-errors.yml diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml index 04acc5f8423..5f68163163e 100644 --- a/app/views/admin/users/_access_levels.html.haml +++ b/app/views/admin/users/_access_levels.html.haml @@ -1,15 +1,18 @@ %fieldset %legend Access .form-group.row - = f.label :projects_limit, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :projects_limit, class: 'col-form-label' .col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control' .form-group.row - = f.label :can_create_group, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :can_create_group, class: 'col-form-label' .col-sm-10= f.check_box :can_create_group .form-group.row - = f.label :access_level, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :access_level, class: 'col-form-label' .col-sm-10 - editing_current_user = (current_user == @user) @@ -29,7 +32,8 @@ You cannot remove your own admin rights. .form-group.row - = f.label :external, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :external, class: 'col-form-label' .col-sm-10 = f.check_box :external do External diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index 58be07fc83e..7f21bdb91c8 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -5,17 +5,20 @@ %fieldset %legend Account .form-group.row - = f.label :name, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :name, class: 'col-form-label' .col-sm-10 = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required .form-group.row - = f.label :username, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :username, class: 'col-form-label' .col-sm-10 = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control' %span.help-inline * required .form-group.row - = f.label :email, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :email, class: 'col-form-label' .col-sm-10 = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required @@ -24,7 +27,8 @@ %fieldset %legend Password .form-group.row - = f.label :password, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :password, class: 'col-form-label' .col-sm-10 %strong Reset link will be generated and sent to the user. @@ -34,10 +38,12 @@ %fieldset %legend Password .form-group.row - = f.label :password, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :password, class: 'col-form-label' .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control' .form-group.row - = f.label :password_confirmation, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :password_confirmation, class: 'col-form-label' .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control' = render partial: 'access_levels', locals: { f: f } @@ -45,21 +51,26 @@ %fieldset %legend Profile .form-group.row - = f.label :avatar, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :avatar, class: 'col-form-label' .col-sm-10 = f.file_field :avatar .form-group.row - = f.label :skype, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :skype, class: 'col-form-label' .col-sm-10= f.text_field :skype, class: 'form-control' .form-group.row - = f.label :linkedin, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :linkedin, class: 'col-form-label' .col-sm-10= f.text_field :linkedin, class: 'form-control' .form-group.row - = f.label :twitter, class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :twitter, class: 'col-form-label' .col-sm-10= f.text_field :twitter, class: 'form-control' .form-group.row - = f.label :website_url, 'Website', class: 'col-form-label col-sm-2' + .col-sm-2.text-right + = f.label :website_url, 'Website', class: 'col-form-label' .col-sm-10= f.text_field :website_url, class: 'form-control' .form-actions diff --git a/changelogs/unreleased/49770-fixes-input-alignment-on-user-admin-form-with-errors.yml b/changelogs/unreleased/49770-fixes-input-alignment-on-user-admin-form-with-errors.yml new file mode 100644 index 00000000000..00e1f6e638a --- /dev/null +++ b/changelogs/unreleased/49770-fixes-input-alignment-on-user-admin-form-with-errors.yml @@ -0,0 +1,5 @@ +--- +title: Fixes input alignment in user admin form with errors +merge_request: 21108 +author: Jacopo Beschi @jacopo-beschi +type: fixed -- cgit v1.2.1 From 0321352bb9e9282423605ef7af915075b3ff9e6e Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 9 Aug 2018 12:41:59 +0100 Subject: Consistent padding but correct label-actions-list positioning and label-links margin --- app/assets/stylesheets/pages/labels.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 81890ec7d33..b25dc4f419a 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -80,7 +80,7 @@ margin-bottom: 5px; display: flex; justify-content: space-between; - padding: $gl-padding $gl-padding 9px; + padding: $gl-padding; border-radius: $border-radius-default; border: 1px solid $theme-gray-100; @@ -253,7 +253,6 @@ text-align: right; padding: 0; position: relative; - top: -3px; margin: 0; } @@ -275,6 +274,7 @@ .label-links { list-style: none; + margin: 0; padding: 0; white-space: nowrap; } -- cgit v1.2.1 From 641d8ec7eac15ba4a91ab529e069cd175874ba57 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 9 Aug 2018 12:53:14 +0100 Subject: Fix missed port --- app/views/projects/mirrors/_mirror_repos_form.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml index 7927e459123..93994cb30ac 100644 --- a/app/views/projects/mirrors/_mirror_repos_form.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml @@ -4,14 +4,14 @@ = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light' = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true -= f.fields_for :remote_mirrors, @remote_mirror do |rm_f| += f.fields_for :remote_mirrors, @project.remote_mirrors.build do |rm_f| = rm_f.hidden_field :enabled, value: '1' = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+" = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden' .form-group = label_tag :auth_method, _('Authentication method'), class: 'label-bold' - = select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method", disabled: true } + = select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method" } .form-group.js-password-group.collapse = label_tag :password, _('Password'), class: 'label-bold' -- cgit v1.2.1 From f8f699ab976801af80221994fbfe148f39070d8a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 9 Aug 2018 12:30:44 +0000 Subject: Update gitlab.pot --- locale/gitlab.pot | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bec60cf592a..29af7785937 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3031,6 +3031,9 @@ msgstr "" msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept." msgstr "" +msgid "Include the username in the URL if required: https://username@gitlab.company.com/group/project.git." +msgstr "" + msgid "Incompatible Project" msgstr "" @@ -5189,6 +5192,9 @@ msgstr "" msgid "Test coverage parsing" msgstr "" +msgid "The Git LFS objects will not be synced." +msgstr "" + msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" msgstr "" @@ -5246,6 +5252,9 @@ msgstr "" msgid "The repository must be accessible over http://, https:// or git://." msgstr "" +msgid "The repository must be accessible over http://, https://, ssh:// and git://." +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "" @@ -5408,6 +5417,9 @@ msgstr "" msgid "This user has no identities" msgstr "" +msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches." +msgstr "" + msgid "Time before an issue gets scheduled" msgstr "" -- cgit v1.2.1 From e92a02ab031998e7a731ab4e2f611f486acc9958 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 9 Aug 2018 13:15:01 +0000 Subject: Fix label item height when no desc --- app/assets/stylesheets/pages/labels.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 2b40404971c..b25dc4f419a 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -253,7 +253,7 @@ text-align: right; padding: 0; position: relative; - top: -3px; + margin: 0; } .label-badge { @@ -274,6 +274,7 @@ .label-links { list-style: none; + margin: 0; padding: 0; white-space: nowrap; } -- cgit v1.2.1 From e40d626b68d58c9870c616a092418673dfa9ea17 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Thu, 9 Aug 2018 13:47:47 +0000 Subject: Add default avatar to group --- app/assets/stylesheets/framework/avatar.scss | 1 + app/assets/stylesheets/pages/projects.scss | 4 -- app/helpers/avatars_helper.rb | 52 ++++++++++------- app/helpers/groups_helper.rb | 5 -- app/views/projects/forks/_fork_button.html.haml | 4 +- .../feat-add-default-avatar-to-group.yml | 5 ++ spec/helpers/avatars_helper_spec.rb | 65 ++++++++++++++++++++-- spec/helpers/groups_helper_spec.rb | 13 ----- 8 files changed, 99 insertions(+), 50 deletions(-) create mode 100644 changelogs/unreleased/feat-add-default-avatar-to-group.yml diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 369556dc24e..4c7c399a3ca 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -103,6 +103,7 @@ display: flex; a { + width: 100%; display: flex; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 944421604fe..6eaa0523387 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -823,10 +823,6 @@ pre.light-well { .avatar-container { align-self: flex-start; - - > a { - width: 100%; - } } .project-details { diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index d48dae8f06d..494f785e305 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -1,28 +1,10 @@ module AvatarsHelper def project_icon(project_id, options = {}) - project = - if project_id.respond_to?(:avatar_url) - project_id - else - Project.find_by_full_path(project_id) - end - - if project.avatar_url - image_tag project.avatar_url, options - else # generated icon - project_identicon(project, options) - end + source_icon(Project, project_id, options) end - def project_identicon(project, options = {}) - bg_key = (project.id % 7) + 1 - options[:class] ||= '' - options[:class] << ' identicon' - options[:class] << " bg#{bg_key}" - - content_tag(:div, class: options[:class]) do - project.name[0, 1].upcase - end + def group_icon(group_id, options = {}) + source_icon(Group, group_id, options) end # Takes both user and email and returns the avatar_icon by @@ -123,4 +105,32 @@ module AvatarsHelper mail_to(options[:user_email], avatar) end end + + private + + def source_icon(klass, source_id, options = {}) + source = + if source_id.respond_to?(:avatar_url) + source_id + else + klass.find_by_full_path(source_id) + end + + if source.avatar_url + image_tag source.avatar_url, options + else + source_identicon(source, options) + end + end + + def source_identicon(source, options = {}) + bg_key = (source.id % 7) + 1 + options[:class] ||= '' + options[:class] << ' identicon' + options[:class] << " bg#{bg_key}" + + content_tag(:div, class: options[:class].strip) do + source.name[0, 1].upcase + end + end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 3c5c8bbd71b..5b51d2f2425 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -33,11 +33,6 @@ module GroupsHelper .count end - def group_icon(group, options = {}) - img_path = group_icon_url(group, options) - image_tag img_path, options - end - def group_icon_url(group, options = {}) if group.is_a?(String) group = Group.find_by_full_path(group) diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml index 8a549d431ee..12cf40bb65f 100644 --- a/app/views/projects/forks/_fork_button.html.haml +++ b/app/views/projects/forks/_fork_button.html.haml @@ -5,7 +5,7 @@ .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked = link_to project_path(forked_project) do - if /no_((\w*)_)*avatar/.match(avatar) - = project_identicon(namespace, class: "avatar s100 identicon") + = project_icon(namespace, class: "avatar s100 identicon") - else .avatar-container.s100 = image_tag(avatar, class: "avatar s100") @@ -18,7 +18,7 @@ class: ("disabled has-tooltip" unless can_create_project), title: (_('You have reached your project limit') unless can_create_project) do - if /no_((\w*)_)*avatar/.match(avatar) - = project_identicon(namespace, class: "avatar s100 identicon") + = project_icon(namespace, class: "avatar s100 identicon") - else .avatar-container.s100 = image_tag(avatar, class: "avatar s100") diff --git a/changelogs/unreleased/feat-add-default-avatar-to-group.yml b/changelogs/unreleased/feat-add-default-avatar-to-group.yml new file mode 100644 index 00000000000..56d8f2ccd6d --- /dev/null +++ b/changelogs/unreleased/feat-add-default-avatar-to-group.yml @@ -0,0 +1,5 @@ +--- +title: Add default avatar to group +merge_request: 17271 +author: George Tsiolis +type: changed diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 5856bccb5b8..55ee87163f9 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -5,12 +5,67 @@ describe AvatarsHelper do let(:user) { create(:user) } - describe '#project_icon' do - it 'returns an url for the avatar' do - project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) + describe '#project_icon & #group_icon' do + shared_examples 'resource with a default avatar' do |source_type| + it 'returns a default avatar div' do + expect(public_send("#{source_type}_icon", *helper_args)) + .to match(%r{
F
}) + end + end + + shared_examples 'resource with a custom avatar' do |source_type| + it 'returns a custom avatar image' do + expect(public_send("#{source_type}_icon", *helper_args)) + .to eq "\"Banana" + end + end + + context 'when providing a project' do + it_behaves_like 'resource with a default avatar', 'project' do + let(:resource) { create(:project, name: 'foo') } + let(:helper_args) { [resource] } + end + + it_behaves_like 'resource with a custom avatar', 'project' do + let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource] } + end + end + + context 'when providing a project path' do + it_behaves_like 'resource with a default avatar', 'project' do + let(:resource) { create(:project, name: 'foo') } + let(:helper_args) { [resource.full_path] } + end - expect(helper.project_icon(project.full_path).to_s) - .to eq "" + it_behaves_like 'resource with a custom avatar', 'project' do + let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource.full_path] } + end + end + + context 'when providing a group' do + it_behaves_like 'resource with a default avatar', 'group' do + let(:resource) { create(:group, name: 'foo') } + let(:helper_args) { [resource] } + end + + it_behaves_like 'resource with a custom avatar', 'group' do + let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource] } + end + end + + context 'when providing a group path' do + it_behaves_like 'resource with a default avatar', 'group' do + let(:resource) { create(:group, name: 'foo') } + let(:helper_args) { [resource.full_path] } + end + + it_behaves_like 'resource with a custom avatar', 'group' do + let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource.full_path] } + end end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 115807f954b..540a8674ec2 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -3,19 +3,6 @@ require 'spec_helper' describe GroupsHelper do include ApplicationHelper - describe 'group_icon' do - it 'returns an url for the avatar' do - avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif') - - group = create(:group) - group.avatar = fixture_file_upload(avatar_file_path) - group.save! - - expect(helper.group_icon(group).to_s) - .to eq "" - end - end - describe 'group_icon_url' do it 'returns an url for the avatar' do avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif') -- cgit v1.2.1 From 1d4d5948ec5b7ae14e132e0b61bb7649942febed Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 9 Aug 2018 15:41:13 +0100 Subject: [ci-skip] add changelog --- .../unreleased/fix-labels-list-item-height-with-no-description.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml diff --git a/changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml b/changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml new file mode 100644 index 00000000000..d215d034917 --- /dev/null +++ b/changelogs/unreleased/fix-labels-list-item-height-with-no-description.yml @@ -0,0 +1,5 @@ +--- +title: Fix label list item container height when there is no label description +merge_request: 21106 +author: +type: fixed -- cgit v1.2.1 From 1ea6d585526688eed63d9b5bdbe343c461f921c9 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Thu, 9 Aug 2018 14:50:01 +0200 Subject: Disable danger in preparation branches Most of these validations don't apply to preparation branches and they cause a lot of noise in the merge request. Therefore disabling danger when the branches look like branches that could be for a preparation MR. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fcf47421b01..fd02d72b4c2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -453,6 +453,7 @@ danger-review: - master variables: - $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/ + - $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/ script: - git version - danger --fail-on-errors=true -- cgit v1.2.1 From c1824ac75a1a3e6a04ac752e70c922528a56e0dc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 8 Aug 2018 17:02:39 -0500 Subject: Fix styling of all card elements --- app/views/admin/users/show.html.haml | 8 ++++---- app/views/projects/pages/_use.html.haml | 4 ++-- app/views/shared/milestones/_issuables.html.haml | 4 ++-- app/views/shared/plugins/_index.html.haml | 4 ++-- changelogs/unreleased/50126-blocked-user-card.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index c7a3df9349a..029efadd75d 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -128,8 +128,8 @@ .col-md-6 - unless @user == current_user - unless @user.confirmed? - .card.bg-info - .card-header + .card.border-info + .card-header.bg-info.text-white Confirm user .card-body - if @user.unconfirmed_email.present? @@ -162,8 +162,8 @@ %br = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning" - if @user.access_locked? - .card.bg-info - .card-header + .card.border-info + .card-header.bg-info.text-white This account has been locked .card-body %p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account. diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml index cd9177c0f9e..988dabef3a0 100644 --- a/app/views/projects/pages/_use.html.haml +++ b/app/views/projects/pages/_use.html.haml @@ -1,6 +1,6 @@ - unless @project.pages_deployed? - .card.bg-info - .card-header + .card.border-info + .card-header.bg-info.text-white Configure pages .card-body %p diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml index ee6354b1c28..ee97f0172da 100644 --- a/app/views/shared/milestones/_issuables.html.haml +++ b/app/views/shared/milestones/_issuables.html.haml @@ -2,8 +2,8 @@ - primary = local_assigns.fetch(:primary, false) - panel_class = primary ? 'bg-primary text-white' : '' -.card{ class: panel_class } - .card-header +.card + .card-header{ class: panel_class } .title = title - if show_counter diff --git a/app/views/shared/plugins/_index.html.haml b/app/views/shared/plugins/_index.html.haml index 7bcc54e7459..9d230d12be2 100644 --- a/app/views/shared/plugins/_index.html.haml +++ b/app/views/shared/plugins/_index.html.haml @@ -19,5 +19,5 @@ .monospace = File.basename(file) - else - %p.card.bg-light.text-center - No plugins found. + .card.bg-light.text-center + .nothing-here-block No plugins found. diff --git a/changelogs/unreleased/50126-blocked-user-card.yml b/changelogs/unreleased/50126-blocked-user-card.yml index b270bf7a757..a42d62e5530 100644 --- a/changelogs/unreleased/50126-blocked-user-card.yml +++ b/changelogs/unreleased/50126-blocked-user-card.yml @@ -1,5 +1,5 @@ --- title: Fix blocked user card style -merge_request: +merge_request: 21095 author: type: fixed -- cgit v1.2.1 From 58b68954b4c0673fffa3bd1e14e412a8c6140709 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 9 Aug 2018 11:07:43 -0700 Subject: Add mock data for spam logs Closes https://gitlab.com/gitlab-org/gitlab-development-kit/issues/387 --- db/fixtures/development/23_spam_logs.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 db/fixtures/development/23_spam_logs.rb diff --git a/db/fixtures/development/23_spam_logs.rb b/db/fixtures/development/23_spam_logs.rb new file mode 100644 index 00000000000..81cc13e6b2d --- /dev/null +++ b/db/fixtures/development/23_spam_logs.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Db + module Fixtures + module Development + class SpamLog + def self.seed + Gitlab::Seeder.quiet do + (::SpamLog.default_per_page + 3).times do |i| + ::SpamLog.create( + user: self.random_user, + user_agent: FFaker::Lorem.sentence, + source_ip: FFaker::Internet.ip_v4_address, + title: FFaker::Lorem.sentence, + description: FFaker::Lorem.paragraph, + via_api: FFaker::Boolean.random, + submitted_as_ham: FFaker::Boolean.random, + recaptcha_verified: FFaker::Boolean.random) + print '.' + end + end + end + + def self.random_user + User.find(User.pluck(:id).sample) + end + end + end + end +end + +Db::Fixtures::Development::SpamLog.seed -- cgit v1.2.1 From 80b77b6d24bea3147ada7a459e3251b66230790c Mon Sep 17 00:00:00 2001 From: Jasper Maes Date: Thu, 9 Aug 2018 19:18:53 +0200 Subject: Rails5 fix specs duplicate key value violates unique constraint 'index_gpg_signatures_on_commit_sha' --- changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml | 5 +++++ spec/features/projects/blobs/shortcuts_blob_spec.rb | 2 +- spec/features/projects/files/user_browses_files_spec.rb | 2 +- spec/features/projects/files/user_deletes_files_spec.rb | 2 +- spec/features/projects/files/user_edits_files_spec.rb | 2 +- spec/features/projects/files/user_replaces_files_spec.rb | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml diff --git a/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml b/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml new file mode 100644 index 00000000000..e31768773b1 --- /dev/null +++ b/changelogs/unreleased/rails5-fix-duplicate-gpg-signature.yml @@ -0,0 +1,5 @@ +--- +title: Rails5 fix specs duplicate key value violates unique constraint 'index_gpg_signatures_on_commit_sha' +merge_request: 21119 +author: Jasper Maes +type: fixed diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb index aeed38aeb76..7203c5b1c21 100644 --- a/spec/features/projects/blobs/shortcuts_blob_spec.rb +++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Blob shortcuts' do +describe 'Blob shortcuts', :js do include TreeHelper let(:project) { create(:project, :public, :repository) } let(:path) { project.repository.ls_files(project.repository.root_ref)[0] } diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index f56174fc85c..612722eeaad 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -210,7 +210,7 @@ describe "User browses files" do end end - context "when browsing a file content" do + context "when browsing a file content", :js do before do visit(tree_path_root_ref) diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb index 0e9f83a16ce..5d37877ccb3 100644 --- a/spec/features/projects/files/user_deletes_files_spec.rb +++ b/spec/features/projects/files/user_deletes_files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Projects > Files > User deletes files' do +describe 'Projects > Files > User deletes files', :js do let(:fork_message) do "You're not allowed to make changes to this project directly. "\ "A fork of this project has been created that you can make changes in, so you can submit a merge request." diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index ccc1bc1bc10..072dc5820c4 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Projects > Files > User edits files' do +describe 'Projects > Files > User edits files', :js do include ProjectForksHelper let(:project) { create(:project, :repository, name: 'Shop') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb index 3a81e77c4ba..3f973338305 100644 --- a/spec/features/projects/files/user_replaces_files_spec.rb +++ b/spec/features/projects/files/user_replaces_files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Projects > Files > User replaces files' do +describe 'Projects > Files > User replaces files', :js do include DropzoneHelper let(:fork_message) do -- cgit v1.2.1 From b786a0f1a0bd8ef54394531248844039568cca19 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 9 Aug 2018 21:14:34 +0100 Subject: Update GitLab Shell to v8.1.1 to fix regressions --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 8104cabd36f..0e79152459e 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -8.1.0 +8.1.1 -- cgit v1.2.1 From 71b62cf140352804760bc5a100929e72bfe5ea23 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 9 Aug 2018 15:27:10 -0500 Subject: Bump pry to 0.11.3; pry-byebug to 3.4.3 --- Gemfile.lock | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1537cacaadd..ee8eed07ed7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -123,7 +123,7 @@ GEM numerizer (~> 0.1.1) chunky_png (1.3.5) citrus (3.0.2) - coderay (1.1.1) + coderay (1.1.2) coercible (1.0.0) descendants_tracker (~> 0.0.1) commonmarker (0.17.8) @@ -494,7 +494,7 @@ GEM memoist (0.16.0) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) - method_source (0.8.2) + method_source (0.9.0) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) @@ -636,12 +636,11 @@ GEM unparser procto (0.0.3) prometheus-client-mmap (0.9.4) - pry (0.10.4) + pry (0.11.3) coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - pry-byebug (3.4.2) - byebug (~> 9.0) + method_source (~> 0.9.0) + pry-byebug (3.4.3) + byebug (>= 9.0, < 9.1) pry (~> 0.10) pry-rails (0.3.5) pry (>= 0.9.10) @@ -872,7 +871,6 @@ GEM simplecov-html (~> 0.10.0) simplecov-html (0.10.0) slack-notifier (1.5.1) - slop (3.6.0) spring (2.0.1) activesupport (>= 4.2) spring-commands-rspec (1.0.4) @@ -1207,4 +1205,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.16.2 + 1.16.3 -- cgit v1.2.1 From 11e08d4bd08482fef68cf620578c006258cd8068 Mon Sep 17 00:00:00 2001 From: Davin Walker Date: Thu, 9 Aug 2018 20:31:19 +0000 Subject: clarify user namespace --- doc/api/projects.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index f360b49c293..bda4164ee92 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -258,8 +258,7 @@ GET /projects?custom_attributes[key]=value&custom_attributes[other_key]=other_va ## List user projects -Get a list of visible projects for the given user. When accessed without -authentication, only public projects are returned. +Get a list of visible projects owned by the given user. When accessed without authentication, only public projects are returned. ``` GET /users/:user_id/projects -- cgit v1.2.1 From a96b2b15b50101caf6974968f73c334822949001 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 9 Aug 2018 13:11:56 -0500 Subject: Separate BS4 overrides into own file; remove all reassignments of -300 --- app/assets/stylesheets/bootstrap_migration.scss | 1 - app/assets/stylesheets/framework.scss | 1 + app/assets/stylesheets/framework/buttons.scss | 2 +- app/assets/stylesheets/framework/dropdowns.scss | 2 +- app/assets/stylesheets/framework/filters.scss | 4 ++-- app/assets/stylesheets/framework/selects.scss | 2 +- app/assets/stylesheets/framework/variables.scss | 20 +------------------- .../stylesheets/framework/variables_overrides.scss | 16 ++++++++++++++++ app/assets/stylesheets/pages/note_form.scss | 2 +- app/assets/stylesheets/pages/search.scss | 4 ++-- 10 files changed, 26 insertions(+), 28 deletions(-) create mode 100644 app/assets/stylesheets/framework/variables_overrides.scss diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index c20738a20c3..056d4b7207a 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -14,7 +14,6 @@ $border-radius-base: 3px !default; $modal-body-bg: $white-light; $input-border: $border-color; -$input-border-focus: $focus-border-color; $padding-base-vertical: $gl-vert-padding; $padding-base-horizontal: $gl-padding; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c46b0b5db09..b1a20c06910 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -1,4 +1,5 @@ @import 'framework/variables'; +@import 'framework/variables_overrides'; @import 'framework/mixins'; @import 'bootstrap'; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 646cedd79ed..ea4798fcefd 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -350,7 +350,7 @@ &:focus { cursor: text; box-shadow: none; - border-color: lighten($dropdown-input-focus-border, 20%); + border-color: lighten($blue-300, 20%); color: $gray-darkest; background-color: $gray-light; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index eebce8b9011..83bc3776178 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -615,7 +615,7 @@ &:focus { color: $dropdown-link-color; - border-color: $dropdown-input-focus-border; + border-color: $blue-300; box-shadow: 0 0 4px $dropdown-input-focus-shadow; ~ .fa { diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 5d79610b21e..9b09ed0ed0a 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -205,7 +205,7 @@ &.focus, &.focus:hover { - border-color: $dropdown-input-focus-border; + border-color: $blue-300; box-shadow: 0 0 4px $search-input-focus-shadow-color; } @@ -294,7 +294,7 @@ &:hover, &:focus { color: $gl-text-color; - border-color: $dropdown-input-focus-border; + border-color: $blue-300; outline: none; } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index b40dcf93969..88d2f0aaf85 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -153,7 +153,7 @@ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; &:focus { - border-color: $input-border-focus; + border-color: $blue-300; } &.select2-active { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 4db9efff6ee..136a5612ddc 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -177,7 +177,6 @@ $border-gray-dark: darken($white-normal, $darken-border-factor); * UI elements */ $border-color: #e5e5e5; -$focus-border-color: $blue-300; $well-expand-item: #e8f2f7; $well-inner-border: #eef0f2; $well-light-border: #f1f1f1; @@ -392,8 +391,7 @@ $dropdown-divider-color: rgba(#000, 0.1); $dropdown-title-btn-color: #bfbfbf; $dropdown-input-color: #555; $dropdown-input-fa-color: #c7c7c7; -$dropdown-input-focus-border: $focus-border-color; -$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, 0.4); +$dropdown-input-focus-shadow: rgba($blue-300, 0.4); $dropdown-loading-bg: rgba(#fff, 0.6); $dropdown-chevron-size: 10px; $dropdown-toggle-active-border-color: darken($border-color, 14%); @@ -834,19 +832,3 @@ Prometheus $prometheus-table-row-highlight-color: $theme-gray-100; $priority-label-empty-state-width: 114px; - -/* - * Override Bootstrap 4 variables - */ - -$secondary: $gray-light; -$input-disabled-bg: $gray-light; -$input-border-color: $theme-gray-200; -$input-color: $gl-text-color; -$font-family-sans-serif: $regular-font; -$font-family-monospace: $monospace-font; -$input-line-height: 20px; -$btn-line-height: 20px; -$table-accent-bg: $gray-light; -$card-border-color: $border-color; -$card-cap-bg: $gray-light; diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss new file mode 100644 index 00000000000..b9c343fa2e9 --- /dev/null +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -0,0 +1,16 @@ +/* + * This file is only for overriding Bootstrap 4 variables. + * Please add any new variables to variables.scss + */ + +$secondary: $gray-light; +$input-disabled-bg: $gray-light; +$input-border-color: $theme-gray-200; +$input-color: $gl-text-color; +$font-family-sans-serif: $regular-font; +$font-family-monospace: $monospace-font; +$input-line-height: 20px; +$btn-line-height: 20px; +$table-accent-bg: $gray-light; +$card-border-color: $border-color; +$card-cap-bg: $gray-light; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index dcf590e7331..8acd64ca1a1 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -69,7 +69,7 @@ .comment-toolbar, .nav-links { - border-color: $focus-border-color; + border-color: $blue-300; } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 60b280fd12e..c9405004c38 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -23,7 +23,7 @@ $search-avatar-size: 16px; .search-text-input:hover, .form-control:hover, :not[readonly] { - border-color: lighten($dropdown-input-focus-border, 20%); + border-color: lighten($blue-300, 20%); box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%); } @@ -127,7 +127,7 @@ input[type='checkbox']:hover { &.search-active { form { @extend .form-control:focus; - border-color: $dropdown-input-focus-border; + border-color: $blue-300; box-shadow: none; @include media-breakpoint-up(xl) { -- cgit v1.2.1 From e01456fd405d54e880b847e27e184da31d8e64b7 Mon Sep 17 00:00:00 2001 From: Jose Vargas Date: Fri, 3 Aug 2018 12:51:54 -0500 Subject: Solve buttons on new file page wrap outside of the container --- app/assets/stylesheets/pages/editor.scss | 16 ++++++++++------ app/views/projects/blob/_editor.html.haml | 2 +- ...e-wrap-outside-of-container-for-long-branch-names.yml | 5 +++++ 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index ddd1f8cc98a..64d5126b783 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -36,6 +36,7 @@ line-height: 35px; padding-top: 7px; padding-bottom: 7px; + display: flex; .float-right { height: 20px; @@ -49,6 +50,7 @@ display: block; float: left; margin-right: 10px; + flex: 1; } .editor-file-name { @@ -60,14 +62,10 @@ .new-file-name { display: inline-block; - max-width: 450px; + max-width: 420px; float: left; @media(max-width: map-get($grid-breakpoints, lg)-1) { - width: 280px; - } - - @media(max-width: map-get($grid-breakpoints, md)-1) { width: 180px; } } @@ -111,9 +109,11 @@ } -@include media-breakpoint-down(xs) { +@include media-breakpoint-down(sm) { .file-editor { .file-title { + display: block; + .float-right { height: auto; } @@ -144,6 +144,10 @@ } } } + + .editor-ref { + max-width: 250px; + } } } diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 8560b72fe85..ec58309055d 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -2,7 +2,7 @@ .file-holder-bottom-radius.file-holder.file.append-bottom-default .js-file-title.file-title.clearfix{ data: { current_action: action } } - .editor-ref + .editor-ref.block-truncated = sprite_icon('fork', size: 12) = ref %span.editor-file-name diff --git a/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml b/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml new file mode 100644 index 00000000000..81ca632947d --- /dev/null +++ b/changelogs/unreleased/47752-buttons-on-new-file-page-wrap-outside-of-container-for-long-branch-names.yml @@ -0,0 +1,5 @@ +--- +title: Fix buttons on the new file page wrapping outside of the container +merge_request: 21015 +author: +type: fixed -- cgit v1.2.1 From 8b134e7dc4a64b0ebc82b508ea948aa7a07ccb99 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 9 Aug 2018 17:17:13 -0500 Subject: Dynamically truncate branch name on larger viewports --- app/assets/stylesheets/pages/editor.scss | 13 +++---------- app/views/projects/blob/_editor.html.haml | 6 +++--- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 64d5126b783..892da152b5f 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -37,10 +37,6 @@ padding-top: 7px; padding-bottom: 7px; display: flex; - - .float-right { - height: 20px; - } } .editor-ref { @@ -50,7 +46,6 @@ display: block; float: left; margin-right: 10px; - flex: 1; } .editor-file-name { @@ -71,7 +66,9 @@ } .file-buttons { - font-size: 0; + display: flex; + flex: 1; + justify-content: flex-end; } .select2 { @@ -113,10 +110,6 @@ .file-editor { .file-title { display: block; - - .float-right { - height: auto; - } } .new-file-name { diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index ec58309055d..24f256d083b 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -5,8 +5,8 @@ .editor-ref.block-truncated = sprite_icon('fork', size: 12) = ref - %span.editor-file-name - - if current_action?(:edit) || current_action?(:update) + - if current_action?(:edit) || current_action?(:update) + %span.editor-file-name = text_field_tag 'file_path', (params[:file_path] || @path), class: 'form-control new-file-path js-file-path-name-input' @@ -16,7 +16,7 @@ = text_field_tag 'file_name', params[:file_name], placeholder: "File name", required: true, class: 'form-control new-file-name js-file-path-name-input' - .float-right.file-buttons + .file-buttons = button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do %span.no-wrap = custom_icon('icon_no_wrap') -- cgit v1.2.1 From 6077c7c122d293dbf8b0d0a2697fd2c09ff19d0f Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Thu, 9 Aug 2018 23:04:05 +0000 Subject: docs: removed duplicate `git_ssh_url` field from build event example Previously the key `git_ssh_url` was listed twice in the example payload of an Build Event web hook. --- doc/user/project/integrations/webhooks.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 8e486318980..77fa517b5b1 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -1122,7 +1122,6 @@ X-Gitlab-Event: Build Hook }, "repository": { "name": "gitlab_test", - "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", "description": "Atque in sunt eos similique dolores voluptatem.", "homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test", "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", -- cgit v1.2.1 From 147b6d172bfca6d5f5ce4fa0716cabe09d0c88c8 Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Thu, 9 Aug 2018 20:45:07 -0400 Subject: solves group overview list items layout regression --- app/assets/stylesheets/pages/groups.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 1587aebfe1d..fa8a0f26b5d 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -419,6 +419,7 @@ .stats { position: relative; line-height: normal; + text-align: right; flex-shrink: 0; > span { @@ -464,7 +465,7 @@ } .last-updated { - position: absolute; + position: relative; right: 12px; min-width: 250px; text-align: right; -- cgit v1.2.1 From 32af384adf2121c5da2e0d063314c91e5c5f6757 Mon Sep 17 00:00:00 2001 From: gfyoung Date: Thu, 9 Aug 2018 23:45:01 -0700 Subject: Enable frozen string in rest of app/models/**/*.rb Partially addresses #47424. --- app/models/programming_language.rb | 2 ++ app/models/project_services/asana_service.rb | 2 ++ app/models/project_services/assembla_service.rb | 2 ++ app/models/project_services/bamboo_service.rb | 2 ++ app/models/project_services/bugzilla_service.rb | 2 ++ app/models/project_services/buildkite_service.rb | 2 ++ .../project_services/builds_email_service.rb | 2 ++ app/models/project_services/campfire_service.rb | 6 ++++-- .../project_services/chat_message/base_message.rb | 2 ++ .../project_services/chat_message/issue_message.rb | 2 ++ .../project_services/chat_message/merge_message.rb | 2 ++ .../project_services/chat_message/note_message.rb | 2 ++ .../chat_message/pipeline_message.rb | 2 ++ .../project_services/chat_message/push_message.rb | 2 ++ .../chat_message/wiki_page_message.rb | 2 ++ .../project_services/chat_notification_service.rb | 2 ++ app/models/project_services/ci_service.rb | 2 ++ .../custom_issue_tracker_service.rb | 2 ++ app/models/project_services/deployment_service.rb | 2 ++ app/models/project_services/drone_ci_service.rb | 2 ++ .../project_services/emails_on_push_service.rb | 2 ++ .../project_services/external_wiki_service.rb | 2 ++ app/models/project_services/flowdock_service.rb | 2 ++ app/models/project_services/gemnasium_service.rb | 2 ++ .../gitlab_issue_tracker_service.rb | 2 ++ .../project_services/hangouts_chat_service.rb | 2 ++ app/models/project_services/hipchat_service.rb | 22 +++++++++++----------- app/models/project_services/irker_service.rb | 2 ++ .../project_services/issue_tracker_service.rb | 2 ++ app/models/project_services/jira_service.rb | 2 ++ app/models/project_services/kubernetes_service.rb | 2 ++ app/models/project_services/mattermost_service.rb | 2 ++ .../mattermost_slash_commands_service.rb | 2 ++ .../project_services/microsoft_teams_service.rb | 2 ++ app/models/project_services/mock_ci_service.rb | 2 ++ .../project_services/mock_deployment_service.rb | 2 ++ .../project_services/mock_monitoring_service.rb | 2 ++ app/models/project_services/monitoring_service.rb | 2 ++ app/models/project_services/packagist_service.rb | 2 ++ .../project_services/pipelines_email_service.rb | 2 ++ .../project_services/pivotaltracker_service.rb | 2 ++ app/models/project_services/prometheus_service.rb | 2 ++ app/models/project_services/pushover_service.rb | 4 +++- app/models/project_services/redmine_service.rb | 2 ++ app/models/project_services/slack_service.rb | 2 ++ .../slack_slash_commands_service.rb | 2 ++ .../project_services/slash_commands_service.rb | 2 ++ app/models/project_services/teamcity_service.rb | 2 ++ app/models/protected_branch/merge_access_level.rb | 2 ++ app/models/protected_branch/push_access_level.rb | 2 ++ app/models/protected_tag/create_access_level.rb | 2 ++ app/models/repository_language.rb | 2 ++ app/models/site_statistic.rb | 2 ++ app/models/storage/hashed_project.rb | 2 ++ app/models/storage/legacy_project.rb | 2 ++ ...en-string-enable-app-models-even-more-still.yml | 5 +++++ 56 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml diff --git a/app/models/programming_language.rb b/app/models/programming_language.rb index 400d6c407a7..0e667dac21e 100644 --- a/app/models/programming_language.rb +++ b/app/models/programming_language.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProgrammingLanguage < ActiveRecord::Base validates :name, presence: true validates :color, allow_blank: false, color: true diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 4f289e6e215..35c19049c04 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'asana' class AsanaService < Service diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 4234b8044e5..60575e45a90 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AssemblaService < Service prop_accessor :token, :subdomain validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index edc5c00d9c4..d502423726c 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BambooService < CiService include ReactiveService diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb index e4e3a80976b..1a2bb6a171b 100644 --- a/app/models/project_services/bugzilla_service.rb +++ b/app/models/project_services/bugzilla_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BugzillaService < IssueTrackerService validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 35884c4560c..43edfde851c 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "addressable/uri" class BuildkiteService < CiService diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index 0c526b53d72..f2295a95b60 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This class is to be removed with 9.1 # We should also by then remove BuildsEmailService from database class BuildsEmailService < Service diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index cb4af73807b..1d7877a1fb5 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CampfireService < Service prop_accessor :token, :subdomain, :room validates :token, presence: true, if: :activated? @@ -82,7 +84,7 @@ class CampfireService < Service before = push[:before] after = push[:after] - message = "" + message = [] message << "[#{project.full_name}] " message << "#{push[:user_name]} " @@ -95,6 +97,6 @@ class CampfireService < Service message << "#{project.web_url}/compare/#{before}...#{after}" end - message + message.join end end diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb index f710fa85b5d..8c68ddc40f2 100644 --- a/app/models/project_services/chat_message/base_message.rb +++ b/app/models/project_services/chat_message/base_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'slack-notifier' module ChatMessage diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb index 3273f41dbd2..0cdcfcf0237 100644 --- a/app/models/project_services/chat_message/issue_message.rb +++ b/app/models/project_services/chat_message/issue_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChatMessage class IssueMessage < BaseMessage attr_reader :title diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb index f412b6833d9..58631e09538 100644 --- a/app/models/project_services/chat_message/merge_message.rb +++ b/app/models/project_services/chat_message/merge_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChatMessage class MergeMessage < BaseMessage attr_reader :merge_request_iid diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb index 7f9486132e6..741474fb27b 100644 --- a/app/models/project_services/chat_message/note_message.rb +++ b/app/models/project_services/chat_message/note_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChatMessage class NoteMessage < BaseMessage attr_reader :note diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb index 96fd23aede3..62aec4351db 100644 --- a/app/models/project_services/chat_message/pipeline_message.rb +++ b/app/models/project_services/chat_message/pipeline_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChatMessage class PipelineMessage < BaseMessage attr_reader :ref_type diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb index 8d599c5f116..82be33a12a1 100644 --- a/app/models/project_services/chat_message/push_message.rb +++ b/app/models/project_services/chat_message/push_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChatMessage class PushMessage < BaseMessage attr_reader :after diff --git a/app/models/project_services/chat_message/wiki_page_message.rb b/app/models/project_services/chat_message/wiki_page_message.rb index d84b80f2de2..b605d289278 100644 --- a/app/models/project_services/chat_message/wiki_page_message.rb +++ b/app/models/project_services/chat_message/wiki_page_message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChatMessage class WikiPageMessage < BaseMessage attr_reader :title diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index a60b4c7fd0d..c10ee07ccf4 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Base class for Chat notifications services # This class is not meant to be used directly, but only to inherit from. class ChatNotificationService < Service diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 82979c8bd34..f0ef2d925ab 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Base class for CI services # List methods you need to implement to get your CI service # working with GitLab Merge Requests diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 456c7f5cee2..b8f8072869c 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CustomIssueTrackerService < IssueTrackerService validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb index 5b8320158fc..6dae4f3a4a6 100644 --- a/app/models/project_services/deployment_service.rb +++ b/app/models/project_services/deployment_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Base class for deployment services # # These services integrate with a deployment solution like Kubernetes/OpenShift, diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index ab4e46da89f..158ae0bf255 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DroneCiService < CiService include ReactiveService diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index b604d860a87..fb73d430fb1 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EmailsOnPushService < Service boolean_accessor :send_from_committer_email boolean_accessor :disable_diffs diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index a4b1ef09e93..d2835c6ac82 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ExternalWikiService < Service prop_accessor :external_wiki_url diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index da01ac1b7cf..2545df06f6b 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "flowdock-git-hook" # Flow dock depends on Grit to compute the number of commits between two given diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 8a6b0ed1a5f..67a92c441b1 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "gemnasium/gitlab_service" class GemnasiumService < Service diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 16e32a4139e..fa9abf58e62 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GitlabIssueTrackerService < IssueTrackerService include Gitlab::Routing diff --git a/app/models/project_services/hangouts_chat_service.rb b/app/models/project_services/hangouts_chat_service.rb index a8512c5f57c..272cd0f4e47 100644 --- a/app/models/project_services/hangouts_chat_service.rb +++ b/app/models/project_services/hangouts_chat_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'hangouts_chat' class HangoutsChatService < ChatNotificationService diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index dce878e485f..66012f0da99 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class HipchatService < Service include ActionView::Helpers::SanitizeHelper @@ -108,7 +110,7 @@ class HipchatService < Service before = push[:before] after = push[:after] - message = "" + message = [] message << "#{push[:user_name]} " if Gitlab::Git.blank_ref?(before) @@ -132,7 +134,7 @@ class HipchatService < Service end end - message + message.join end def markdown(text, options = {}) @@ -165,11 +167,11 @@ class HipchatService < Service description = obj_attr[:description] issue_link = "issue ##{issue_iid}" - message = "#{user_name} #{state} #{issue_link} in #{project_link}: #{title}" + message = ["#{user_name} #{state} #{issue_link} in #{project_link}: #{title}"] message << "
#{markdown(description)}
" - message + message.join end def create_merge_request_message(data) @@ -184,12 +186,11 @@ class HipchatService < Service merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" merge_request_link = "merge request !#{merge_request_id}" - message = "#{user_name} #{state} #{merge_request_link} in " \ - "#{project_link}: #{title}" + message = ["#{user_name} #{state} #{merge_request_link} in " \ + "#{project_link}: #{title}"] message << "
#{markdown(description)}
" - - message + message.join end def format_title(title) @@ -235,12 +236,11 @@ class HipchatService < Service end subject_html = "#{subject_type} #{subject_desc}" - message = "#{user_name} commented on #{subject_html} in #{project_link}: " + message = ["#{user_name} commented on #{subject_html} in #{project_link}: "] message << title message << "
#{markdown(note, ref: commit_id)}
" - - message + message.join end def create_pipeline_message(data) diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 27bdf708c80..a783a314071 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'uri' class IrkerService < Service diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index df6dcd90985..c7520d766a8 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssueTrackerService < Service validate :one_issue_tracker, if: :activated?, on: :manual_change diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 82d438d5378..cc98b3f5a41 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class JiraService < IssueTrackerService include Gitlab::Routing include ApplicationHelper diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 722642f6da7..bda1f67b8ff 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # NOTE: # We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic. diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb index 0362ed172c7..b8bc83b870e 100644 --- a/app/models/project_services/mattermost_service.rb +++ b/app/models/project_services/mattermost_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MattermostService < ChatNotificationService def title 'Mattermost notifications' diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 227d430083d..ca324f68d2d 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MattermostSlashCommandsService < SlashCommandsService include TriggersHelper diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb index 99500caec0e..5b0e5fed092 100644 --- a/app/models/project_services/microsoft_teams_service.rb +++ b/app/models/project_services/microsoft_teams_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MicrosoftTeamsService < ChatNotificationService def title 'Microsoft Teams Notification' diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb index b89dc07a73e..6883976f0c8 100644 --- a/app/models/project_services/mock_ci_service.rb +++ b/app/models/project_services/mock_ci_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service class MockCiService < CiService ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb index 59a3811ce5d..7ab1687f8ba 100644 --- a/app/models/project_services/mock_deployment_service.rb +++ b/app/models/project_services/mock_deployment_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MockDeploymentService < DeploymentService def title 'Mock deployment' diff --git a/app/models/project_services/mock_monitoring_service.rb b/app/models/project_services/mock_monitoring_service.rb index ed0318c6b27..bcf8f1df5da 100644 --- a/app/models/project_services/mock_monitoring_service.rb +++ b/app/models/project_services/mock_monitoring_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MockMonitoringService < MonitoringService def title 'Mock monitoring' diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb index 9af68b4e821..1b530a8247b 100644 --- a/app/models/project_services/monitoring_service.rb +++ b/app/models/project_services/monitoring_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Base class for monitoring services # # These services integrate with a deployment solution like Prometheus diff --git a/app/models/project_services/packagist_service.rb b/app/models/project_services/packagist_service.rb index ba62a5b7ac0..003884bb7ac 100644 --- a/app/models/project_services/packagist_service.rb +++ b/app/models/project_services/packagist_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PackagistService < Service prop_accessor :username, :token, :server diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 4cf149ac044..6f39a5e6e83 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PipelinesEmailService < Service prop_accessor :recipients boolean_accessor :notify_only_broken_pipelines diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index 3476e7d2283..617e502b639 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PivotaltrackerService < Service API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index df4254e0523..509e5b6089b 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PrometheusService < MonitoringService include PrometheusAdapter diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index 8777a44b72f..4e48c348b45 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PushoverService < Service BASE_URI = 'https://api.pushover.net/1'.freeze @@ -79,7 +81,7 @@ class PushoverService < Service end if data[:total_commits_count] > 0 - message << "\nTotal commits count: #{data[:total_commits_count]}" + message = [message, "Total commits count: #{data[:total_commits_count]}"].join("\n") end pushover_data = { diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index 3721093a6d1..a80be4b06da 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RedmineService < IssueTrackerService validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 71da0af75f6..482808255f9 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SlackService < ChatNotificationService def title 'Slack notifications' diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 1c3892a3f75..6c82e088231 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SlackSlashCommandsService < SlashCommandsService include TriggersHelper diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index 37ea45109ae..e3ab60adefd 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Base class for Chat services # This class is not meant to be used directly, but only to inherrit from. class SlashCommandsService < Service diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 802678147cf..eeeff5e802a 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class TeamcityService < CiService include ReactiveService diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index e8d35ac326f..b0d5c64e931 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base include ProtectedBranchAccess end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 7a2e9e5ec5d..b2a88229853 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProtectedBranch::PushAccessLevel < ActiveRecord::Base include ProtectedBranchAccess end diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb index 6b6ab3d8279..b06e55fb5dd 100644 --- a/app/models/protected_tag/create_access_level.rb +++ b/app/models/protected_tag/create_access_level.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProtectedTag::CreateAccessLevel < ActiveRecord::Base include ProtectedTagAccess diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb index f467d4eafa3..b18142a2ac4 100644 --- a/app/models/repository_language.rb +++ b/app/models/repository_language.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepositoryLanguage < ActiveRecord::Base belongs_to :project belongs_to :programming_language diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb index 9c9c3172fe6..daac1c57db9 100644 --- a/app/models/site_statistic.rb +++ b/app/models/site_statistic.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SiteStatistic < ActiveRecord::Base # prevents the creation of multiple rows default_value_for :id, 1 diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb index 26b4b78ac64..90710f73fd3 100644 --- a/app/models/storage/hashed_project.rb +++ b/app/models/storage/hashed_project.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Storage class HashedProject attr_accessor :project diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb index 27cb388c702..9f6f19acb41 100644 --- a/app/models/storage/legacy_project.rb +++ b/app/models/storage/legacy_project.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Storage class LegacyProject attr_accessor :project diff --git a/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml b/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml new file mode 100644 index 00000000000..a77f3baeed3 --- /dev/null +++ b/changelogs/unreleased/frozen-string-enable-app-models-even-more-still.yml @@ -0,0 +1,5 @@ +--- +title: Enable frozen string in rest of app/models/**/*.rb +merge_request: gfyoung +author: +type: performance -- cgit v1.2.1 From 03351199dfa793631f352201d2dba15162789ec1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 10 Aug 2018 09:04:45 +0100 Subject: Reduce differences between CE and EE code base in reports components --- app/assets/javascripts/reports/components/issues_list.vue | 4 ++-- app/assets/javascripts/reports/components/report_issues.vue | 4 ++-- changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/fl-reduce-ee-conflicts-reports-code.yml diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue index dbb8848d1fa..df42201b5de 100644 --- a/app/assets/javascripts/reports/components/issues_list.vue +++ b/app/assets/javascripts/reports/components/issues_list.vue @@ -1,10 +1,10 @@ + diff --git a/changelogs/unreleased/50101-stuck-component.yml b/changelogs/unreleased/50101-stuck-component.yml new file mode 100644 index 00000000000..bfe4009a2b3 --- /dev/null +++ b/changelogs/unreleased/50101-stuck-component.yml @@ -0,0 +1,5 @@ +--- +title: Creates Vvue component for warning block about stuck runners +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4c660ae45c1..c5bd27754b7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2729,6 +2729,9 @@ msgstr "" msgid "Go back" msgstr "" +msgid "Go to" +msgstr "" + msgid "Go to %{link_to_google_takeout}." msgstr "" @@ -4630,6 +4633,9 @@ msgstr "" msgid "Runners can be placed on separate users, servers, and even on your local machine." msgstr "" +msgid "Runners page" +msgstr "" + msgid "Running" msgstr "" diff --git a/spec/javascripts/jobs/stuck_block_spec.js b/spec/javascripts/jobs/stuck_block_spec.js new file mode 100644 index 00000000000..4e2108dfdfb --- /dev/null +++ b/spec/javascripts/jobs/stuck_block_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; +import component from '~/jobs/components/stuck_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Stuck Block Job component', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('with no runners for project', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: true, + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders only information about project not having runners', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).not.toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); + + describe('with tags', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: false, + tags: ['docker', 'gitlab-org'], + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders information about the tags not being set', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).not.toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); + }); + + it('renders tags', () => { + expect(vm.$el.textContent).toContain('docker'); + expect(vm.$el.textContent).toContain('gitlab-org'); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); + + describe('without active runners', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: false, + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders information about project not having runners', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).not.toBeNull(); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); +}); -- cgit v1.2.1 From 2b36c84176951441ea41d6195a20d33ae6fbf7d5 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 10 Aug 2018 15:06:44 +0100 Subject: Moves terminal button into Vue. Updates permission checks to use paths instead - backend already handles permissions --- .../jobs/components/sidebar_details_block.vue | 38 +++++++----- app/assets/javascripts/jobs/job_details_bundle.js | 2 +- app/views/projects/jobs/_sidebar.html.haml | 8 +-- .../javascripts/jobs/sidebar_details_block_spec.js | 67 ++++++++++++++-------- 4 files changed, 69 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index d2adf628050..36d4a3e2bc9 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -1,14 +1,16 @@ + diff --git a/changelogs/unreleased/50101-erased-block.yml b/changelogs/unreleased/50101-erased-block.yml new file mode 100644 index 00000000000..5a5c9bc0fc4 --- /dev/null +++ b/changelogs/unreleased/50101-erased-block.yml @@ -0,0 +1,5 @@ +--- +title: Creates vue component for erased block on job view +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 92f424ccdf0..aeb9efb528f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3130,6 +3130,12 @@ msgstr "" msgid "Jobs" msgstr "" +msgid "Job|Job has been erased" +msgstr "" + +msgid "Job|Job has been erased by" +msgstr "" + msgid "Jul" msgstr "" diff --git a/spec/javascripts/jobs/erased_block_spec.js b/spec/javascripts/jobs/erased_block_spec.js new file mode 100644 index 00000000000..7cf32e984a2 --- /dev/null +++ b/spec/javascripts/jobs/erased_block_spec.js @@ -0,0 +1,56 @@ +import Vue from 'vue'; +import { getTimeago } from '~/lib/utils/datetime_utility'; +import component from '~/jobs/components/erased_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Erased block', () => { + const Component = Vue.extend(component); + let vm; + + const erasedAt = '2016-11-07T11:11:16.525Z'; + const timeago = getTimeago(); + const formatedDate = timeago.format(erasedAt); + + afterEach(() => { + vm.$destroy(); + }); + + describe('with job erased by user', () => { + beforeEach(() => { + vm = mountComponent(Component, { + erasedByUser: true, + username: 'root', + linkToUser: 'gitlab.com/root', + erasedAt, + }); + }); + + it('renders username and link', () => { + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('gitlab.com/root'); + + expect(vm.$el.textContent).toContain('Job has been erased by'); + expect(vm.$el.textContent).toContain('root'); + }); + + it('renders erasedAt', () => { + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); + + describe('with erased job', () => { + beforeEach(() => { + vm = mountComponent(Component, { + erasedByUser: false, + erasedAt, + }); + }); + + it('renders username and link', () => { + expect(vm.$el.textContent).toContain('Job has been erased'); + }); + + it('renders erasedAt', () => { + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); +}); -- cgit v1.2.1 From 1a5af91ad4228c1406d0633780529b9994d6cc73 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 13 Aug 2018 11:42:47 +0100 Subject: Fixed small scroll area in Web IDE Closes #50242 --- app/assets/javascripts/ide/components/ide_tree_list.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue index 5611b37be7c..00ae5ea2c15 100644 --- a/app/assets/javascripts/ide/components/ide_tree_list.vue +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -61,7 +61,7 @@ export default {
Date: Mon, 13 Aug 2018 12:45:11 +0200 Subject: Fixed ESLint error --- app/assets/javascripts/diffs/store/mutations.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 50234483135..0b538cd7982 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -27,9 +27,7 @@ export default { let u = 0; for (u = 0; u < linesLength; u += 1) { const line = file.parallelDiffLines[u]; - // eslint-disable-next-line no-param-reassign if (line.left) delete line.left.text; - // eslint-disable-next-line no-param-reassign if (line.right) delete line.right.text; } } @@ -39,7 +37,6 @@ export default { let u = 0; for (u = 0; u < linesLength; u += 1) { const line = file.highlightedDiffLines[u]; - // eslint-disable-next-line no-param-reassign delete line.text; } } -- cgit v1.2.1 From d1787d476f9e36056053b611f26b218cd0f6a0e8 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Mon, 13 Aug 2018 11:40:18 +0100 Subject: Vendor Auto-DevOps.gitlab-ci.yml with new proxy env vars passed through to docker See https://gitlab.com/gitlab-org/gitlab-ci-yml/merge_requests/184 Thanks to @kinolaev for this contribution. --- .../50243-auto-devops-behind-a-proxy.yml | 6 ++++++ vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 23 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml diff --git a/changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml b/changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml new file mode 100644 index 00000000000..0f6208d0c7a --- /dev/null +++ b/changelogs/unreleased/50243-auto-devops-behind-a-proxy.yml @@ -0,0 +1,6 @@ +--- +title: Vendor Auto-DevOps.gitlab-ci.yml with new proxy env vars passed through to + docker +merge_request: 21159 +author: kinolaev +type: added diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index b698248bc38..dae80c6542d 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -720,10 +720,29 @@ rollout 100%: if [[ -f Dockerfile ]]; then echo "Building Dockerfile-based application..." - docker build -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" . + docker build \ + --build-arg HTTP_PROXY="$HTTP_PROXY" \ + --build-arg http_proxy="$http_proxy" \ + --build-arg HTTPS_PROXY="$HTTPS_PROXY" \ + --build-arg https_proxy="$https_proxy" \ + --build-arg FTP_PROXY="$FTP_PROXY" \ + --build-arg ftp_proxy="$ftp_proxy" \ + --build-arg NO_PROXY="$NO_PROXY" \ + --build-arg no_proxy="$no_proxy" \ + -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" . else echo "Building Heroku-based application using gliderlabs/herokuish docker image..." - docker run -i -e BUILDPACK_URL --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build + docker run -i \ + -e BUILDPACK_URL \ + -e HTTP_PROXY \ + -e http_proxy \ + -e HTTPS_PROXY \ + -e https_proxy \ + -e FTP_PROXY \ + -e ftp_proxy \ + -e NO_PROXY \ + -e no_proxy \ + --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" docker rm "$CI_CONTAINER_NAME" >/dev/null echo "" -- cgit v1.2.1 From db86214e6afb3123c71b935f2e5c335114df218b Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Mon, 13 Aug 2018 10:58:53 -0400 Subject: Update docs and compress images --- .../web_ide/img/admin_clientside_evaluation.png | Bin 8312 -> 9342 bytes .../project/web_ide/img/clientside_evaluation.png | Bin 84603 -> 60256 bytes doc/user/project/web_ide/index.md | 28 +++++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/doc/user/project/web_ide/img/admin_clientside_evaluation.png b/doc/user/project/web_ide/img/admin_clientside_evaluation.png index 1cffaf09005..a930490398b 100644 Binary files a/doc/user/project/web_ide/img/admin_clientside_evaluation.png and b/doc/user/project/web_ide/img/admin_clientside_evaluation.png differ diff --git a/doc/user/project/web_ide/img/clientside_evaluation.png b/doc/user/project/web_ide/img/clientside_evaluation.png index 56b65dde73a..bd04d3d644b 100644 Binary files a/doc/user/project/web_ide/img/clientside_evaluation.png and b/doc/user/project/web_ide/img/clientside_evaluation.png differ diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index 992f97e2284..16969b2c527 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -72,28 +72,30 @@ leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list of branches. You will need to commit or discard all your changes before switching to a different branch. -## Clientside Evaluation +## Client Side Evaluation > [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19764) [GitLab Core][ce] 11.2. -The Web IDE can be used to preview JavaScript projects right in the browser. This -feature uses CodeSandbox to compile and bundle the JavaScript used to preview. -On public projects, a `Open in CodeSandbox` button visible which will take -the files and contents of the project and load it into a CodeSandbox project. +The Web IDE can be used to preview JavaScript projects right in the browser. +This feature uses CodeSandbox to compile and bundle the JavaScript used to +preview the web application. On public projects, an `Open in CodeSandbox` +button is visible which will transfer the contents of the project into a +CodeSandbox project to share with others. **Note** this button is not visible on private or internal projects. -![Web IDE Clientside Evaluation](img/clientside_evaluation.png) +![Web IDE Client Side Evaluation](img/clientside_evaluation.png) -### Enabling Clientside Evaluation +### Enabling Client Side Evaluation -The Clientside Evaluation feature needs to be enabled inside of the GitLab instances -admin settings. +The Client Side Evaluation feature needs to be enabled in the GitLab instances +admin settings. Client Side Evaluation is enabled for all projects on +GitLab.com -![Admin Clientside Evaluation setting](img/admin_clientside_evaluation.png) +![Admin Client Side Evaluation setting](img/admin_clientside_evaluation.png) -Once it has been enabled in application settings, projects with a `package.json` file -and a `main` entry point can be previewed inside of the Web IDE. An example `package.json` -is below. +Once it has been enabled in application settings, projects with a +`package.json` file and a `main` entry point can be previewed inside of the Web +IDE. An example `package.json` is below. ```json { -- cgit v1.2.1 From 5d2b2ad362a927d8cc0548687cb0ca2e64f4b87f Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Mon, 13 Aug 2018 15:45:16 +0000 Subject: Resolve "`Copy to Clipboard` tooltip appears under modal" --- app/assets/stylesheets/framework/modal.scss | 1 - app/assets/stylesheets/performance_bar.scss | 2 +- .../41996-copy-to-clipboard-tooltip-appears-under-modal.yml | 5 +++++ 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index ffb40166c15..7d53a631cdf 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -79,7 +79,6 @@ body.modal-open { .modal { background-color: $black-transparent; - z-index: 2100; @include media-breakpoint-up(md) { .modal-dialog { diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 7a93c4dfa28..57d43beaf21 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -6,7 +6,7 @@ left: 0; top: 0; width: 100%; - z-index: 2000; + z-index: 1039; height: $performance-bar-height; background: $black; diff --git a/changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml b/changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml new file mode 100644 index 00000000000..e452a91d8b7 --- /dev/null +++ b/changelogs/unreleased/41996-copy-to-clipboard-tooltip-appears-under-modal.yml @@ -0,0 +1,5 @@ +--- +title: Solve tooltip appears under modal +merge_request: 21017 +author: +type: fixed -- cgit v1.2.1 From 994cc25b61909825de09a04ff24feac11d1ff333 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Mon, 13 Aug 2018 15:49:27 +0000 Subject: Improve visuals of language bar on projects --- app/assets/stylesheets/pages/projects.scss | 15 +++++++++++++-- .../unreleased/visual-improvements-language-bar.yml | 5 +++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/visual-improvements-language-bar.yml diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 6eaa0523387..6d9f415e869 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -754,8 +754,19 @@ } .repository-languages-bar { - height: 6px; - margin-bottom: 8px; + height: 8px; + margin-bottom: $gl-padding-8; + background-color: $white-light; + border-radius: $border-radius-default; + + .progress-bar { + margin-right: 2px; + padding: 0 $gl-padding-4; + + &:last-child { + margin-right: 0; + } + } } pre.light-well { diff --git a/changelogs/unreleased/visual-improvements-language-bar.yml b/changelogs/unreleased/visual-improvements-language-bar.yml new file mode 100644 index 00000000000..23cae22b962 --- /dev/null +++ b/changelogs/unreleased/visual-improvements-language-bar.yml @@ -0,0 +1,5 @@ +--- +title: Improve visuals of language bar on projects +merge_request: 21006 +author: +type: changed -- cgit v1.2.1 From c82d61d9bfabbc3829fec6640b6ba4485de73f36 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Aug 2018 16:52:42 +0100 Subject: Optimize timeago tooltip logic --- app/assets/javascripts/jobs/components/erased_block.vue | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue index 2a6c9c49b2f..d688eebfa95 100644 --- a/app/assets/javascripts/jobs/components/erased_block.vue +++ b/app/assets/javascripts/jobs/components/erased_block.vue @@ -32,24 +32,17 @@ export default {
+ +
-- cgit v1.2.1 From 3b0a3a915daab96d9ccfafd1567c9045b6741334 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 13 Aug 2018 18:47:09 +0200 Subject: Added Changelog --- changelogs/unreleased/tz-mr-incremental-rendering.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/tz-mr-incremental-rendering.yml diff --git a/changelogs/unreleased/tz-mr-incremental-rendering.yml b/changelogs/unreleased/tz-mr-incremental-rendering.yml new file mode 100644 index 00000000000..ee4184a9215 --- /dev/null +++ b/changelogs/unreleased/tz-mr-incremental-rendering.yml @@ -0,0 +1,5 @@ +--- +title: Hey DZ, I added a feature to GitLab! +merge_request: +author: +type: performance -- cgit v1.2.1 From 32196a7fbed89171fa9be9bd937cd79bf9766ba7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 13 Aug 2018 17:58:27 +0100 Subject: Creates Vue component for job log trace --- app/assets/javascripts/jobs/components/job_log.vue | 33 ++++++++++++++++ changelogs/unreleased/50101-job-log-component.yml | 5 +++ spec/javascripts/jobs/job_log_spec.js | 45 ++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 app/assets/javascripts/jobs/components/job_log.vue create mode 100644 changelogs/unreleased/50101-job-log-component.yml create mode 100644 spec/javascripts/jobs/job_log_spec.js diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue new file mode 100644 index 00000000000..3c4749d996b --- /dev/null +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -0,0 +1,33 @@ + + diff --git a/changelogs/unreleased/50101-job-log-component.yml b/changelogs/unreleased/50101-job-log-component.yml new file mode 100644 index 00000000000..0759e7cfbd9 --- /dev/null +++ b/changelogs/unreleased/50101-job-log-component.yml @@ -0,0 +1,5 @@ +--- +title: Creates vue component for job log trace +merge_request: +author: +type: other diff --git a/spec/javascripts/jobs/job_log_spec.js b/spec/javascripts/jobs/job_log_spec.js new file mode 100644 index 00000000000..406f1c4ccc5 --- /dev/null +++ b/spec/javascripts/jobs/job_log_spec.js @@ -0,0 +1,45 @@ +import Vue from 'vue'; +import component from '~/jobs/components/job_log.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Job Log', () => { + const Component = Vue.extend(component); + let vm; + + const trace = 'Running with gitlab-runner 11.1.0 (081978aa)
on docker-auto-scale-com d5ae8d25
Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29 ...
'; + + afterEach(() => { + vm.$destroy(); + }); + + it('renders provided trace', () => { + vm = mountComponent(Component, { + trace, + isReceivingBuildTrace: true, + }); + + expect(vm.$el.querySelector('code').textContent).toContain('Running with gitlab-runner 11.1.0 (081978aa)'); + }); + + describe('while receiving trace', () => { + it('renders animation', () => { + vm = mountComponent(Component, { + trace, + isReceivingBuildTrace: true, + }); + + expect(vm.$el.querySelector('.js-log-animation')).not.toBeNull(); + }); + }); + + describe('when build trace has finishes', () => { + it('does not render animation', () => { + vm = mountComponent(Component, { + trace, + isReceivingBuildTrace: false, + }); + + expect(vm.$el.querySelector('.js-log-animation')).toBeNull(); + }); + }); +}); -- cgit v1.2.1 From b6a2e6553b8c0f15b79677fcc83a3e17856e65b1 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Mon, 13 Aug 2018 19:35:07 +0200 Subject: Added the actual description --- changelogs/unreleased/tz-mr-incremental-rendering.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/changelogs/unreleased/tz-mr-incremental-rendering.yml b/changelogs/unreleased/tz-mr-incremental-rendering.yml index ee4184a9215..a35fa200363 100644 --- a/changelogs/unreleased/tz-mr-incremental-rendering.yml +++ b/changelogs/unreleased/tz-mr-incremental-rendering.yml @@ -1,5 +1,4 @@ ---- -title: Hey DZ, I added a feature to GitLab! -merge_request: +title: Incremental rendering with Vue on merge request page +merge_request: 21063 author: type: performance -- cgit v1.2.1 From f73a21e35710eef24b47307631f1996b35fdf9d3 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Fri, 18 May 2018 15:20:23 +0300 Subject: Add Noto Color Emoji font support This is a font from [Google](https://www.google.com/get/noto/). Arch Linux [has a recommendation of it](https://wiki.archlinux.org/index.php/fonts#Emoji_and_symbols). Add info about this into `docs/user/markdown.md#emoji` --- app/assets/javascripts/emoji/support/unicode_support_map.js | 2 +- app/assets/stylesheets/framework/emojis.scss | 2 +- app/assets/stylesheets/framework/variables.scss | 2 +- changelogs/unreleased/add_google_noto_color_emoji_font.yml | 5 +++++ doc/user/markdown.md | 4 ++++ 5 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/add_google_noto_color_emoji_font.yml diff --git a/app/assets/javascripts/emoji/support/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js index 8c1861c56db..651169391fe 100644 --- a/app/assets/javascripts/emoji/support/unicode_support_map.js +++ b/app/assets/javascripts/emoji/support/unicode_support_map.js @@ -86,7 +86,7 @@ function generateUnicodeSupportMap(testMap) { canvas.height = numTestEntries * fontSize; ctx.fillStyle = '#000000'; ctx.textBaseline = 'middle'; - ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`; + ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`; // Write each emoji to the canvas vertically let writeIndex = 0; testMapKeys.forEach(testKey => { diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss index 3cde0490371..a8ec1e1145a 100644 --- a/app/assets/stylesheets/framework/emojis.scss +++ b/app/assets/stylesheets/framework/emojis.scss @@ -2,7 +2,7 @@ gl-emoji { font-style: normal; display: inline-flex; vertical-align: middle; - font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1.5em; line-height: 0.9; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index efc54196b75..baae4772708 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -374,7 +374,7 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%); $monospace-font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, - 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; /* * Dropdowns diff --git a/changelogs/unreleased/add_google_noto_color_emoji_font.yml b/changelogs/unreleased/add_google_noto_color_emoji_font.yml new file mode 100644 index 00000000000..9ba46262767 --- /dev/null +++ b/changelogs/unreleased/add_google_noto_color_emoji_font.yml @@ -0,0 +1,5 @@ +--- +title: Add Noto Color Emoji font support +merge_request: 19036 +author: Alexander Popov +type: changed diff --git a/doc/user/markdown.md b/doc/user/markdown.md index bd199b55a61..ea6acecc387 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -234,6 +234,8 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: + Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. + Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: :zap: You can use emoji anywhere GFM is supported. :v: @@ -244,6 +246,8 @@ If you are new to this, don't be :fearful:. You can easily join the emoji :famil Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: +Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. + ### Special GitLab References GFM recognizes special references. -- cgit v1.2.1 From 89ec4a3e449983010d8aa9e666d8402c3eab9e27 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 13 Aug 2018 12:06:54 -0700 Subject: Bump rugged to 0.27.4 for security fixes See https://github.com/libgit2/libgit2/releases for more details. --- Gemfile.lock | 2 +- changelogs/unreleased/sh-bump-rugged-0-27-4.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/sh-bump-rugged-0-27-4.yml diff --git a/Gemfile.lock b/Gemfile.lock index ee8eed07ed7..62c3b28f386 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -810,7 +810,7 @@ GEM rubyzip (1.2.1) rufus-scheduler (3.4.0) et-orbi (~> 1.0) - rugged (0.27.2) + rugged (0.27.4) safe_yaml (1.0.4) sanitize (4.6.6) crass (~> 1.0.2) diff --git a/changelogs/unreleased/sh-bump-rugged-0-27-4.yml b/changelogs/unreleased/sh-bump-rugged-0-27-4.yml new file mode 100644 index 00000000000..50373cb81ad --- /dev/null +++ b/changelogs/unreleased/sh-bump-rugged-0-27-4.yml @@ -0,0 +1,5 @@ +--- +title: Bump rugged to 0.27.4 for security fixes +merge_request: +author: +type: security -- cgit v1.2.1 From 2659d4e11a12712e4e2582836cdb8bee71ac3857 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 14 Aug 2018 02:14:51 +0200 Subject: Upgrade Middleman template for GitLab CI --- vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml index 9f4cc0574d6..983d7b5250e 100644 --- a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml @@ -1,24 +1,25 @@ # Full project: https://gitlab.com/pages/middleman -image: ruby:2.3 +image: ruby:2.4 +variables: + LANG: "C.UTF-8" cache: paths: - vendor -test: - script: +before_script: - apt-get update -yqqq - apt-get install -y nodejs - bundle install --path vendor + +test: + script: - bundle exec middleman build except: - master pages: script: - - apt-get update -yqqq - - apt-get install -y nodejs - - bundle install --path vendor - bundle exec middleman build artifacts: paths: -- cgit v1.2.1 From 8411d1cffc05171e82d727d883f03e279c8e9e05 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 23 Jul 2018 10:42:19 +0800 Subject: Add email_events to replace EMAIL_EVENTS because it needs to be dynamic, allowing override for EE. --- .../notification_settings_controller.rb | 8 ++--- app/helpers/notifications_helper.rb | 2 +- app/models/notification_setting.rb | 8 +++++ app/services/notification_recipient_service.rb | 2 +- .../notifications/_custom_notifications.html.haml | 2 +- doc/api/notification_settings.md | 2 +- lib/api/entities.rb | 2 +- lib/api/notification_settings.rb | 4 +-- .../notification_settings_controller_spec.rb | 9 +++--- spec/models/notification_setting_spec.rb | 36 ++++++++++++++++++++-- 10 files changed, 57 insertions(+), 18 deletions(-) diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index ed20302487c..461f26561f1 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -5,14 +5,14 @@ class NotificationSettingsController < ApplicationController return render_404 unless can_read?(resource) @notification_setting = current_user.notification_settings_for(resource) - @saved = @notification_setting.update(notification_setting_params) + @saved = @notification_setting.update(notification_setting_params_for(resource)) render_response end def update @notification_setting = current_user.notification_settings.find(params[:id]) - @saved = @notification_setting.update(notification_setting_params) + @saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source)) render_response end @@ -42,8 +42,8 @@ class NotificationSettingsController < ApplicationController } end - def notification_setting_params - allowed_fields = NotificationSetting::EMAIL_EVENTS.dup + def notification_setting_params_for(source) + allowed_fields = NotificationSetting.email_events(source).dup allowed_fields << :level params.require(:notification_setting).permit(allowed_fields) end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 3e42063224e..7ff201a321b 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -83,7 +83,7 @@ module NotificationsHelper end def notification_event_name(event) - # All values from NotificationSetting::EMAIL_EVENTS + # All values from NotificationSetting.email_events case event when :success_pipeline s_('NotificationEvent|Successful pipeline') diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 1df3a51a7fc..c5d49bd00ca 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -45,6 +45,14 @@ class NotificationSetting < ActiveRecord::Base :success_pipeline ].freeze + def self.email_events(source = nil) + EMAIL_EVENTS + end + + def email_events + self.class.email_events(source) + end + EXCLUDED_PARTICIPATING_EVENTS = [ :success_pipeline ].freeze diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 4389fd89538..1d6c45c515b 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -279,7 +279,7 @@ module NotificationRecipientService end # Build event key to search on custom notification level - # Check NotificationSetting::EMAIL_EVENTS + # Check NotificationSetting.email_events def custom_action @custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym end diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml index 1f6e8f98bbb..43a87fd8397 100644 --- a/app/views/shared/notifications/_custom_notifications.html.haml +++ b/app/views/shared/notifications/_custom_notifications.html.haml @@ -19,7 +19,7 @@ - paragraph = _('Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.') % { notification_link: notification_link.html_safe } #{ paragraph.html_safe } .col-lg-8 - - NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index| + - notification_setting.email_events.each_with_index do |event, index| - field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]" .form-group .form-check{ class: ("prepend-top-0" if index == 0) } diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md index 682b90361bd..165b9a11c7a 100644 --- a/doc/api/notification_settings.md +++ b/doc/api/notification_settings.md @@ -15,7 +15,7 @@ mention custom ``` -If the `custom` level is used, specific email events can be controlled. Notification email events are defined in the `NotificationSetting::EMAIL_EVENTS` model variable. Currently, these events are recognized: +If the `custom` level is used, specific email events can be controlled. Available events are returned by `NotificationSetting.email_events`. Currently, these events are recognized: ``` new_note diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 27f28e1df93..453ebb9c669 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -856,7 +856,7 @@ module API class NotificationSetting < Grape::Entity expose :level expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do - ::NotificationSetting::EMAIL_EVENTS.each do |event| + ::NotificationSetting.email_events.each do |event| expose event end end diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb index 0266bf2f717..9c824d9953c 100644 --- a/lib/api/notification_settings.rb +++ b/lib/api/notification_settings.rb @@ -23,7 +23,7 @@ module API params do optional :level, type: String, desc: 'The global notification level' optional :notification_email, type: String, desc: 'The email address to send notifications' - NotificationSetting::EMAIL_EVENTS.each do |event| + NotificationSetting.email_events.each do |event| optional event, type: Boolean, desc: 'Enable/disable this notification' end end @@ -73,7 +73,7 @@ module API end params do optional :level, type: String, desc: "The #{source_type} notification level" - NotificationSetting::EMAIL_EVENTS.each do |event| + NotificationSetting.email_events(source_type.to_sym).each do |event| optional event, type: Boolean, desc: 'Enable/disable this notification' end end diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index e133950e684..a3356a86d4b 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -21,10 +21,11 @@ describe NotificationSettingsController do end context 'when authorized' do + let(:notification_setting) { user.notification_settings_for(source) } let(:custom_events) do events = {} - NotificationSetting::EMAIL_EVENTS.each do |event| + NotificationSetting.email_events(source).each do |event| events[event.to_s] = true end @@ -36,7 +37,7 @@ describe NotificationSettingsController do end context 'for projects' do - let(:notification_setting) { user.notification_settings_for(project) } + let(:source) { project } it 'creates notification setting' do post :create, @@ -67,7 +68,7 @@ describe NotificationSettingsController do end context 'for groups' do - let(:notification_setting) { user.notification_settings_for(group) } + let(:source) { group } it 'creates notification setting' do post :create, @@ -145,7 +146,7 @@ describe NotificationSettingsController do let(:custom_events) do events = {} - NotificationSetting::EMAIL_EVENTS.each do |event| + notification_setting.email_events.each do |event| events[event] = "true" end end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 77c475b9f52..e545b674b4f 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -94,9 +94,39 @@ RSpec.describe NotificationSetting do end end - context 'email events' do - it 'includes EXCLUDED_WATCHER_EVENTS in EMAIL_EVENTS' do - expect(described_class::EMAIL_EVENTS).to include(*described_class::EXCLUDED_WATCHER_EVENTS) + describe '.email_events' do + subject { described_class.email_events } + + it 'returns email events' do + expect(subject).to include( + :new_note, + :new_issue, + :reopen_issue, + :close_issue, + :reassign_issue, + :new_merge_request, + :reopen_merge_request, + :close_merge_request, + :reassign_merge_request, + :merge_merge_request, + :failed_pipeline, + :success_pipeline + ) + end + + it 'includes EXCLUDED_WATCHER_EVENTS' do + expect(subject).to include(*described_class::EXCLUDED_WATCHER_EVENTS) + end + end + + describe '#email_events' do + let(:source) { build(:group) } + + subject { build(:notification_setting, source: source) } + + it 'calls email_events' do + expect(described_class).to receive(:email_events).with(source) + subject.email_events end end end -- cgit v1.2.1 From e07a1a5bb43f659f5289a7b317a081465945af4d Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 23 Jul 2018 20:51:42 +0800 Subject: Allow global event list containing all events This way we can globally set group only ee events too. Use nil to indicate global. --- lib/api/notification_settings.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb index 9c824d9953c..bf0d6b9e434 100644 --- a/lib/api/notification_settings.rb +++ b/lib/api/notification_settings.rb @@ -50,7 +50,9 @@ module API end end - %w[group project].each do |source_type| + [Group, Project].each do |source_class| + source_type = source_class.name.underscore + params do requires :id, type: String, desc: "The #{source_type} ID" end @@ -73,7 +75,7 @@ module API end params do optional :level, type: String, desc: "The #{source_type} notification level" - NotificationSetting.email_events(source_type.to_sym).each do |event| + NotificationSetting.email_events(source_class).each do |event| optional event, type: Boolean, desc: 'Enable/disable this notification' end end -- cgit v1.2.1 From 59564df15f5fc0893fcd09cfb00d8c8e6534696d Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 30 Jul 2018 17:38:20 +0800 Subject: Allow NotificationRecipientService::Builder::Default to handle target without project (e.g. Epic) --- app/services/notification_recipient_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 1d6c45c515b..1172fdbea8d 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -130,7 +130,7 @@ module NotificationRecipientService end def add_project_watchers - add_recipients(project_watchers, :watch, nil) + add_recipients(project_watchers, :watch, nil) if project end def add_group_watchers -- cgit v1.2.1 From 048f78e961fc166a7999e32facfc7306fa6d5add Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 10 Aug 2018 16:11:09 +0530 Subject: Change `Backlog` list title to `Open` --- app/assets/javascripts/boards/models/list.js | 3 ++- locale/gitlab.pot | 3 +++ spec/features/boards/boards_spec.rb | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 050cbd8db48..ad473404c29 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -1,6 +1,7 @@ /* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */ /* global ListIssue */ +import { __ } from '~/locale'; import ListLabel from '~/vue_shared/models/label'; import ListAssignee from '~/vue_shared/models/assignee'; import queryData from '../utils/query_data'; @@ -30,7 +31,7 @@ class List { this.id = obj.id; this._uid = this.guid(); this.position = obj.position; - this.title = obj.title; + this.title = obj.list_type === 'backlog' ? __('Open') : obj.title; this.type = obj.list_type; const typeInfo = this.getTypeInfo(this.type); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e8bf7ae8f0e..b58c5f68673 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3848,6 +3848,9 @@ msgstr "" msgid "Oops, are you sure?" msgstr "" +msgid "Open" +msgstr "" + msgid "Open in Xcode" msgstr "" diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index a0af2dea3ad..baa2b1d8af5 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -44,7 +44,7 @@ describe 'Issue Boards', :js do end it 'creates default lists' do - lists = ['Backlog', 'To Do', 'Doing', 'Closed'] + lists = ['Open', 'To Do', 'Doing', 'Closed'] page.within(find('.board-blank-state')) do click_button('Add default lists') -- cgit v1.2.1 From 73e572ef2f624b68b4767ba5e6ab88e47ab765d1 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 10 Aug 2018 16:19:18 +0530 Subject: Add changelog entry --- .../unreleased/48942-rename-backlog-list-to-open-issue-boards.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml diff --git a/changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml b/changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml new file mode 100644 index 00000000000..26851fc2dec --- /dev/null +++ b/changelogs/unreleased/48942-rename-backlog-list-to-open-issue-boards.yml @@ -0,0 +1,5 @@ +--- +title: Change 'Backlog' list title to 'Open' in Issue Boards +merge_request: 21131 +author: +type: changed -- cgit v1.2.1 From 046bc2bff1965c70093a158813126c1c28a8e525 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 10 Aug 2018 16:37:23 +0530 Subject: Update docs to change `Backlog` issue board list to `Open` --- doc/api/boards.md | 2 +- doc/api/group_boards.md | 2 +- doc/user/project/img/issue_board.png | Bin 100684 -> 327718 bytes doc/user/project/issue_board.md | 8 ++++---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api/boards.md b/doc/api/boards.md index 246de50323e..5f006f4f012 100644 --- a/doc/api/boards.md +++ b/doc/api/boards.md @@ -144,7 +144,7 @@ Example response: ## List board lists Get a list of the board's lists. -Does not include `backlog` and `closed` lists +Does not include `open` and `closed` lists ``` GET /projects/:id/boards/:board_id/lists diff --git a/doc/api/group_boards.md b/doc/api/group_boards.md index 45a8544d6b1..373904e50c4 100644 --- a/doc/api/group_boards.md +++ b/doc/api/group_boards.md @@ -119,7 +119,7 @@ Example response: ## List board lists Get a list of the board's lists. -Does not include `backlog` and `closed` lists +Does not include `open` and `closed` lists ``` GET /groups/:id/boards/:board_id/lists diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png index 50e051e25a0..925b969eebe 100644 Binary files a/doc/user/project/img/issue_board.png and b/doc/user/project/img/issue_board.png differ diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 49b49271cff..0e847be79c2 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -119,10 +119,10 @@ Issue Board, that is, create or delete lists and drag issues from one list to an ## Issue Board terminology - **Issue Board** - Each board represents a unique view for your issues. It can have multiple lists with each list consisting of issues represented by cards. -- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Backlog' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee. On the top of that list you can see the number of issues that belong to it. +- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Open' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee. On the top of that list you can see the number of issues that belong to it. - **Label list**: a list based on a label. It shows all opened issues with that label. - **Assignee list**: a list which includes all issues assigned to a user. - - **Backlog** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list. + - **Open** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list. - **Closed** (default): shows all closed issues. Always appears as the rightmost list. - **Card** - A box in the list that represents an individual issue. The information you can see on a card consists of the issue number, the issue title, the assignee, and the labels associated with the issue. You can drag cards from one list to another to change their label or assignee from that of the source list to that of the destination list. @@ -353,9 +353,9 @@ To remove an assignee list, just as with a label list, click the trash icon. When dragging issues between lists, different behavior occurs depending on the source list and the target list. -| | To Backlog | To Closed | To label `B` list | To assignee `Bob` list | +| | To Open | To Closed | To label `B` list | To assignee `Bob` list | | --- | --- | --- | --- | --- | -| From Backlog | - | Issue closed | `B` added | `Bob` assigned | +| From Open | - | Issue closed | `B` added | `Bob` assigned | | From Closed | Issue reopened | - | Issue reopened
`B` added | Issue reopened
`Bob` assigned | | From label `A` list | `A` removed | Issue closed | `A` removed
`B` added | `Bob` assigned | | From assignee `Alice` list | `Alice` unassigned | Issue closed | `B` added | `Alice` unassigned
`Bob` assigned | -- cgit v1.2.1 From 3d93a77220d3dc8c6c5aa882a769496d13cc2455 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Tue, 14 Aug 2018 09:09:49 +0100 Subject: Fix flaky auto devops QA test by waiting longer for production This fixes https://gitlab.com/gitlab-org/quality/nightly/issues/3 because the code_quality job holds up the production job. Waiting longer should reduce the likelihood of this. --- qa/qa/specs/features/project/auto_devops_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/qa/specs/features/project/auto_devops_spec.rb b/qa/qa/specs/features/project/auto_devops_spec.rb index bc713b46d81..c2c3bef98e4 100644 --- a/qa/qa/specs/features/project/auto_devops_spec.rb +++ b/qa/qa/specs/features/project/auto_devops_spec.rb @@ -50,7 +50,7 @@ module QA Page::Project::Pipeline::Show.perform do |pipeline| expect(pipeline).to have_build('build', status: :success, wait: 600) expect(pipeline).to have_build('test', status: :success, wait: 600) - expect(pipeline).to have_build('production', status: :success, wait: 600) + expect(pipeline).to have_build('production', status: :success, wait: 1200) end end end -- cgit v1.2.1 From 8ab5642c25cc6b2751d82759cd2654ccf14c89c1 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Tue, 14 Aug 2018 09:15:00 +0100 Subject: Auto-DevOps.gitlab-ci.yml: Update glibc package signing key URL Vendored in from https://gitlab.com/gitlab-org/gitlab-ci-yml/merge_requests/185 Thanks @sgerrand --- changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml | 5 +++++ vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml diff --git a/changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml b/changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml new file mode 100644 index 00000000000..e081dfe6093 --- /dev/null +++ b/changelogs/unreleased/50257-fix-auto-devops-glibc-pubkey-url.yml @@ -0,0 +1,5 @@ +--- +title: 'Auto-DevOps.gitlab-ci.yml: Update glibc package signing key URL' +merge_request: 21182 +author: sgerrand +type: fixed diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index dae80c6542d..39876805ffa 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -641,7 +641,7 @@ rollout 100%: function install_dependencies() { apk add -U openssl curl tar gzip bash ca-certificates git - wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub + wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk apk add glibc-2.23-r3.apk rm glibc-2.23-r3.apk -- cgit v1.2.1 From baa1d40c846cafe25601219e68699e77ffd8093d Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 14 Aug 2018 10:23:07 +0200 Subject: Added compute property to diff file + changed a for loop --- app/assets/javascripts/diffs/components/diff_file.vue | 5 ++++- app/assets/javascripts/diffs/store/mutations.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index e887a71c551..59e9ba08b8b 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -46,6 +46,9 @@ export default { showExpandMessage() { return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge; }, + showLoadingIcon() { + return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed); + }, }, methods: { ...mapActions('diffs', ['loadCollapsedDiff']), @@ -133,7 +136,7 @@ export default { :diff-file="file" />
Date: Tue, 14 Aug 2018 10:34:15 +0200 Subject: Remove feature gate for ListNewCommits Introduced by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20087, this has been tested on .com now, and is stable. Closes https://gitlab.com/gitlab-org/gitaly/issues/1286 Closes https://gitlab.com/gitlab-org/gitaly/issues/1233 --- lib/gitlab/git/repository.rb | 13 ++----------- spec/models/repository_spec.rb | 42 ++++++++++++++++-------------------------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 3e11355435b..e9c901f8592 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -366,18 +366,9 @@ module Gitlab end end - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233 def new_commits(newrev) - gitaly_migrate(:new_commits) do |is_enabled| - if is_enabled - gitaly_ref_client.list_new_commits(newrev) - else - refs = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - rev_list(including: newrev, excluding: :all).split("\n").map(&:strip) - end - - Gitlab::Git::Commit.batch_by_oid(self, refs) - end + wrapped_gitaly_errors do + gitaly_ref_client.list_new_commits(newrev) end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 52ec8dbe25a..2859d5149ec 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -296,41 +296,31 @@ describe Repository do end describe '#new_commits' do - shared_examples 'finding unreferenced commits' do - set(:project) { create(:project, :repository) } - let(:repository) { project.repository } + set(:project) { create(:project, :repository) } + let(:repository) { project.repository } - subject { repository.new_commits(rev) } + subject { repository.new_commits(rev) } - context 'when there are no new commits' do - let(:rev) { repository.commit.id } + context 'when there are no new commits' do + let(:rev) { repository.commit.id } - it 'returns an empty array' do - expect(subject).to eq([]) - end + it 'returns an empty array' do + expect(subject).to eq([]) end + end - context 'when new commits are found' do - let(:branch) { 'orphaned-branch' } - let!(:rev) { repository.commit(branch).id } + context 'when new commits are found' do + let(:branch) { 'orphaned-branch' } + let!(:rev) { repository.commit(branch).id } - it 'returns the commits' do - repository.delete_branch(branch) + it 'returns the commits' do + repository.delete_branch(branch) - expect(subject).not_to be_empty - expect(subject).to all( be_a(::Commit) ) - expect(subject.size).to eq(1) - end + expect(subject).not_to be_empty + expect(subject).to all( be_a(::Commit) ) + expect(subject.size).to eq(1) end end - - context 'when Gitaly handles the request' do - it_behaves_like 'finding unreferenced commits' - end - - context 'when Gitaly is disabled', :disable_gitaly do - it_behaves_like 'finding unreferenced commits' - end end describe '#commits_by' do -- cgit v1.2.1 From bf88e9afe70e6589dd0c1c089a5a73c8d2b687c6 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 30 Jul 2018 18:30:36 +0800 Subject: Allow extensible mention type action for EE --- app/services/notification_recipient_service.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 1172fdbea8d..5c0e8a35cb0 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -220,6 +220,8 @@ module NotificationRecipientService end class Default < Base + MENTION_TYPE_ACTIONS = [:new_issue, :new_merge_request].freeze + attr_reader :target attr_reader :current_user attr_reader :action @@ -252,7 +254,7 @@ module NotificationRecipientService add_subscribed_users - if [:new_issue, :new_merge_request].include?(custom_action) + if self.class.mention_type_actions.include?(custom_action) # These will all be participants as well, but adding with the :mention # type ensures that users with the mention notification level will # receive them, too. @@ -283,6 +285,10 @@ module NotificationRecipientService def custom_action @custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym end + + def self.mention_type_actions + MENTION_TYPE_ACTIONS.dup + end end class NewNote < Base -- cgit v1.2.1 From f8ee861cd42b35ff5d35c18dafbe4d1c425af926 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Wed, 8 Aug 2018 14:38:39 +0800 Subject: Move N_ calls into separate files These are dynamic translations, so has to be marked explicitly using `N_`, but they are not used in runtime, so can exist in separate file. https://github.com/grosser/gettext_i18n_rails#unfound-translations-with-rake-gettextfind --- .rubocop.yml | 2 ++ app/helpers/notifications_helper.rb | 10 ---------- app/models/notification_setting.rb | 1 + locale/unfound_translations.rb | 16 ++++++++++++++++ 4 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 locale/unfound_translations.rb diff --git a/.rubocop.yml b/.rubocop.yml index a586190319b..9858bbe0ddd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -48,6 +48,8 @@ Naming/FileName: - 'qa/bin/*' - 'config/**/*' - 'lib/generators/**/*' + - 'locale/unfound_translations.rb' + - 'ee/locale/unfound_translations.rb' - 'ee/lib/generators/**/*' IgnoreExecutableScripts: true AllowedAcronyms: diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 7ff201a321b..a185f2916d4 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -88,16 +88,6 @@ module NotificationsHelper when :success_pipeline s_('NotificationEvent|Successful pipeline') else - N_('NotificationEvent|New note') - N_('NotificationEvent|New issue') - N_('NotificationEvent|Reopen issue') - N_('NotificationEvent|Close issue') - N_('NotificationEvent|Reassign issue') - N_('NotificationEvent|New merge request') - N_('NotificationEvent|Close merge request') - N_('NotificationEvent|Reassign merge request') - N_('NotificationEvent|Merge merge request') - N_('NotificationEvent|Failed pipeline') s_(event.to_s.humanize) end end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index c5d49bd00ca..1600acfc575 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -45,6 +45,7 @@ class NotificationSetting < ActiveRecord::Base :success_pipeline ].freeze + # Update unfound_translations.rb when events are changed def self.email_events(source = nil) EMAIL_EVENTS end diff --git a/locale/unfound_translations.rb b/locale/unfound_translations.rb new file mode 100644 index 00000000000..0826d64049b --- /dev/null +++ b/locale/unfound_translations.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Dynamic translations which needs to be marked by `N_` so they can be found by `rake gettext:find`, see: +# https://github.com/grosser/gettext_i18n_rails#unfound-translations-with-rake-gettextfind + +# NotificationSetting.email_events +N_('NotificationEvent|New note') +N_('NotificationEvent|New issue') +N_('NotificationEvent|Reopen issue') +N_('NotificationEvent|Close issue') +N_('NotificationEvent|Reassign issue') +N_('NotificationEvent|New merge request') +N_('NotificationEvent|Close merge request') +N_('NotificationEvent|Reassign merge request') +N_('NotificationEvent|Merge merge request') +N_('NotificationEvent|Failed pipeline') -- cgit v1.2.1 From 82bc5687ced2191583cf6b77f51cd0486be9e0cb Mon Sep 17 00:00:00 2001 From: Chantal Rollison Date: Mon, 13 Aug 2018 10:50:12 -0700 Subject: Modified find project method to return nil if project not found --- app/views/search/results/_blob.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index fdcd126e7a3..a8d4d4af93a 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,4 +1,6 @@ - project = find_project_for_result_blob(blob) +- return unless project + - file_name, blob = parse_search_result(blob) - blob_link = project_blob_path(project, tree_join(blob.ref, file_name)) -- cgit v1.2.1 From 3a80f030375835f0cfaf105ae6aba7c103f63de5 Mon Sep 17 00:00:00 2001 From: Peter Marko Date: Wed, 18 Jul 2018 23:46:56 +0200 Subject: Expose all artifacts sizes in jobs api --- app/models/ci/job_artifact.rb | 2 +- .../expose-all-artifacts-sizes-in-jobs-api.yml | 5 ++ doc/api/jobs.md | 22 +++++++ lib/api/entities.rb | 6 ++ lib/api/jobs.rb | 4 +- spec/requests/api/jobs_spec.rb | 74 +++++++++++++++++++--- 6 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index d7c5f29be96..17b7ee4f07e 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -33,7 +33,7 @@ module Ci where(file_type: types) end - delegate :exists?, :open, to: :file + delegate :filename, :exists?, :open, to: :file enum file_type: { archive: 1, diff --git a/changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml b/changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml new file mode 100644 index 00000000000..1453d39934b --- /dev/null +++ b/changelogs/unreleased/expose-all-artifacts-sizes-in-jobs-api.yml @@ -0,0 +1,5 @@ +--- +title: Expose all artifacts sizes in jobs api +merge_request: 20821 +author: Peter Marko +type: added diff --git a/doc/api/jobs.md b/doc/api/jobs.md index b0b698efc8c..4bf65a8fafd 100644 --- a/doc/api/jobs.md +++ b/doc/api/jobs.md @@ -44,6 +44,7 @@ Example of response "status": "pending" }, "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": "2015-12-24T17:54:24.729Z", @@ -81,6 +82,12 @@ Example of response "filename": "artifacts.zip", "size": 1000 }, + "artifacts": [ + {"file_type": "archive", "size": 1000, "filename": "artifacts.zip", "file_format": "zip"}, + {"file_type": "metadata", "size": 186, "filename": "metadata.gz", "file_format": "gzip"}, + {"file_type": "trace", "size": 1500, "filename": "job.log", "file_format": "raw"}, + {"file_type": "junit", "size": 750, "filename": "junit.xml.gz", "file_format": "gzip"} + ], "finished_at": "2015-12-24T17:54:27.895Z", "artifacts_expire_at": "2016-01-23T17:54:27.895Z", "id": 7, @@ -92,6 +99,7 @@ Example of response "status": "pending" }, "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": "2015-12-24T17:54:27.722Z", @@ -161,6 +169,7 @@ Example of response "status": "pending" }, "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": "2015-12-24T17:54:24.729Z", @@ -198,6 +207,12 @@ Example of response "filename": "artifacts.zip", "size": 1000 }, + "artifacts": [ + {"file_type": "archive", "size": 1000, "filename": "artifacts.zip", "file_format": "zip"}, + {"file_type": "metadata", "size": 186, "filename": "metadata.gz", "file_format": "gzip"}, + {"file_type": "trace", "size": 1500, "filename": "job.log", "file_format": "raw"}, + {"file_type": "junit", "size": 750, "filename": "junit.xml.gz", "file_format": "gzip"} + ], "finished_at": "2015-12-24T17:54:27.895Z", "artifacts_expire_at": "2016-01-23T17:54:27.895Z", "id": 7, @@ -209,6 +224,7 @@ Example of response "status": "pending" }, "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": "2015-12-24T17:54:27.722Z", @@ -276,6 +292,7 @@ Example of response "status": "pending" }, "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": "2015-12-24T17:54:30.733Z", @@ -459,6 +476,7 @@ Example of response "id": 42, "name": "rubocop", "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": null, @@ -505,6 +523,7 @@ Example of response "id": 42, "name": "rubocop", "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": null, @@ -554,6 +573,7 @@ Example of response "id": 42, "name": "rubocop", "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "created_at": "2016-01-11T10:13:33.506Z", @@ -605,6 +625,7 @@ Example response: "id": 42, "name": "rubocop", "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "created_at": "2016-01-11T10:13:33.506Z", @@ -653,6 +674,7 @@ Example of response "id": 42, "name": "rubocop", "ref": "master", + "artifacts": [], "runner": null, "stage": "test", "started_at": null, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 27f28e1df93..1648094cfde 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1080,6 +1080,10 @@ module API expose :filename, :size end + class JobArtifact < Grape::Entity + expose :file_type, :size, :filename, :file_format + end + class JobBasic < Grape::Entity expose :id, :status, :stage, :name, :ref, :tag, :coverage expose :created_at, :started_at, :finished_at @@ -1094,7 +1098,9 @@ module API end class Job < JobBasic + # artifacts_file is included in job_artifacts, but kept for backward compatibility (remove in api/v5) expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? } + expose :job_artifacts, as: :artifacts, using: JobArtifact expose :runner, with: Runner expose :artifacts_expire_at end diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 10c6e565f09..fc8c52085ab 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -38,7 +38,7 @@ module API builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) - builds = builds.preload(:user, :job_artifacts_archive, :runner, pipeline: :project) + builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, pipeline: :project) present paginate(builds), with: Entities::Job end @@ -54,7 +54,7 @@ module API pipeline = user_project.pipelines.find(params[:pipeline_id]) builds = pipeline.builds builds = filter_builds(builds, params[:scope]) - builds = builds.preload(:job_artifacts_archive, project: [:namespace]) + builds = builds.preload(:job_artifacts_archive, :job_artifacts, project: [:namespace]) present paginate(builds), with: Entities::Job end diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 5814d834572..6adbbb40489 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -3,6 +3,32 @@ require 'spec_helper' describe API::Jobs do include HttpIOHelpers + shared_examples 'a job with artifacts and trace' do |result_is_array: true| + context 'with artifacts and trace' do + let!(:second_job) { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) } + + it 'returns artifacts and trace data', :skip_before_request do + get api(api_endpoint, api_user) + json_job = result_is_array ? json_response.select { |job| job['id'] == second_job.id }.first : json_response + + expect(json_job['artifacts_file']).not_to be_nil + expect(json_job['artifacts_file']).not_to be_empty + expect(json_job['artifacts_file']['filename']).to eq(second_job.artifacts_file.filename) + expect(json_job['artifacts_file']['size']).to eq(second_job.artifacts_file.size) + expect(json_job['artifacts']).not_to be_nil + expect(json_job['artifacts']).to be_an Array + expect(json_job['artifacts'].size).to eq(second_job.job_artifacts.length) + json_job['artifacts'].each do |artifact| + expect(artifact).not_to be_nil + file_type = Ci::JobArtifact.file_types[artifact['file_type']] + expect(artifact['size']).to eq(second_job.job_artifacts.where(file_type: file_type).first.size) + expect(artifact['filename']).to eq(second_job.job_artifacts.where(file_type: file_type).first.filename) + expect(artifact['file_format']).to eq(second_job.job_artifacts.where(file_type: file_type).first.file_format) + end + end + end + end + set(:project) do create(:project, :repository, public_builds: false) end @@ -49,6 +75,20 @@ describe API::Jobs do expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) end + context 'without artifacts and trace' do + it 'returns no artifacts nor trace data' do + json_job = json_response.first + + expect(json_job['artifacts_file']).to be_nil + expect(json_job['artifacts']).to be_an Array + expect(json_job['artifacts']).to be_empty + end + end + + it_behaves_like 'a job with artifacts and trace' do + let(:api_endpoint) { "/projects/#{project.id}/jobs" } + end + it 'returns pipeline data' do json_job = json_response.first @@ -60,7 +100,7 @@ describe API::Jobs do end it 'avoids N+1 queries', :skip_before_request do - first_build = create(:ci_build, :artifacts, pipeline: pipeline) + first_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) first_build.runner = create(:ci_runner) first_build.user = create(:user) first_build.save @@ -68,7 +108,7 @@ describe API::Jobs do control_count = ActiveRecord::QueryRecorder.new { go }.count second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) - second_build = create(:ci_build, :artifacts, pipeline: second_pipeline) + second_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: second_pipeline) second_build.runner = create(:ci_runner) second_build.user = create(:user) second_build.save @@ -117,9 +157,11 @@ describe API::Jobs do describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do let(:query) { Hash.new } - before do - job - get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + before do |example| + unless example.metadata[:skip_before_request] + job + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + end end context 'authorized user' do @@ -133,6 +175,13 @@ describe API::Jobs do expect(json_response).not_to be_empty expect(json_response.first['commit']['id']).to eq project.commit.id expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) + expect(json_response.first['artifacts_file']).to be_nil + expect(json_response.first['artifacts']).to be_an Array + expect(json_response.first['artifacts']).to be_empty + end + + it_behaves_like 'a job with artifacts and trace' do + let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" } end it 'returns pipeline data' do @@ -183,7 +232,7 @@ describe API::Jobs do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query end.count - 3.times { create(:ci_build, :artifacts, pipeline: pipeline) } + 3.times { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) } expect do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query @@ -201,8 +250,10 @@ describe API::Jobs do end describe 'GET /projects/:id/jobs/:job_id' do - before do - get api("/projects/#{project.id}/jobs/#{job.id}", api_user) + before do |example| + unless example.metadata[:skip_before_request] + get api("/projects/#{project.id}/jobs/#{job.id}", api_user) + end end context 'authorized user' do @@ -219,10 +270,17 @@ describe API::Jobs do expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at) expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at) expect(Time.parse(json_response['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) + expect(json_response['artifacts_file']).to be_nil + expect(json_response['artifacts']).to be_an Array + expect(json_response['artifacts']).to be_empty expect(json_response['duration']).to eq(job.duration) expect(json_response['web_url']).to be_present end + it_behaves_like 'a job with artifacts and trace', result_is_array: false do + let(:api_endpoint) { "/projects/#{project.id}/jobs/#{second_job.id}" } + end + it 'returns pipeline data' do json_job = json_response -- cgit v1.2.1 From 581a946f5a977a500e55d586bfddc36a722fd659 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 14 Aug 2018 19:21:48 +0300 Subject: Backport of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6860 --- spec/factories/uploads.rb | 7 +++++++ spec/lib/gitlab/cleanup/project_uploads_spec.rb | 2 ++ 2 files changed, 9 insertions(+) diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index a81b2169b89..81c485fba1a 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -46,6 +46,13 @@ FactoryBot.define do secret SecureRandom.hex end + trait :favicon_upload do + model { build(:appearance) } + path { File.join(secret, filename) } + uploader "FaviconUploader" + secret SecureRandom.hex + end + trait :attachment_upload do transient do mount_point :attachment diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb index 37b38776775..11e605eece6 100644 --- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb @@ -244,9 +244,11 @@ describe Gitlab::Cleanup::ProjectUploads do orphaned1 = create(:upload, :personal_snippet_upload, :with_file) orphaned2 = create(:upload, :namespace_upload, :with_file) orphaned3 = create(:upload, :attachment_upload, :with_file) + orphaned4 = create(:upload, :favicon_upload, :with_file) paths << orphaned1.absolute_path paths << orphaned2.absolute_path paths << orphaned3.absolute_path + paths << orphaned4.absolute_path Upload.delete_all expect(logger).not_to receive(:info).with(/move|fix/i) -- cgit v1.2.1 From 65827c426819850e12261f32c004bd9571d2355b Mon Sep 17 00:00:00 2001 From: Annabel Gray Date: Tue, 14 Aug 2018 16:44:08 +0000 Subject: Remove scss variables, part 2 --- app/assets/stylesheets/bootstrap_migration.scss | 2 +- app/assets/stylesheets/framework/buttons.scss | 4 ++-- app/assets/stylesheets/framework/common.scss | 2 +- app/assets/stylesheets/framework/files.scss | 6 ++--- app/assets/stylesheets/framework/flash.scss | 4 ++-- app/assets/stylesheets/framework/forms.scss | 2 +- .../stylesheets/framework/markdown_area.scss | 2 +- app/assets/stylesheets/framework/typography.scss | 10 ++++---- app/assets/stylesheets/framework/variables.scss | 15 +++--------- app/assets/stylesheets/framework/zen.scss | 2 +- app/assets/stylesheets/page_bundles/ide.scss | 2 +- app/assets/stylesheets/pages/boards.scss | 2 +- app/assets/stylesheets/pages/builds.scss | 2 +- app/assets/stylesheets/pages/commits.scss | 4 ++-- app/assets/stylesheets/pages/cycle_analytics.scss | 2 +- app/assets/stylesheets/pages/diff.scss | 4 ++-- app/assets/stylesheets/pages/environments.scss | 4 ++-- app/assets/stylesheets/pages/graph.scss | 4 ++-- app/assets/stylesheets/pages/issuable.scss | 10 ++++---- app/assets/stylesheets/pages/issues.scss | 4 ++-- app/assets/stylesheets/pages/labels.scss | 6 ++--- app/assets/stylesheets/pages/milestone.scss | 2 +- app/assets/stylesheets/pages/note_form.scss | 8 +++---- app/assets/stylesheets/pages/notes.scss | 28 +++++++++++----------- app/assets/stylesheets/pages/pipelines.scss | 2 +- app/assets/stylesheets/pages/projects.scss | 4 ++-- app/assets/stylesheets/pages/search.scss | 2 +- 27 files changed, 65 insertions(+), 74 deletions(-) diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index 056d4b7207a..e8e707cf90c 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -85,7 +85,7 @@ strong { } a { - color: $gl-link-color; + color: $blue-600; } hr { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index ea4798fcefd..0dc7aa4ef68 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -434,7 +434,7 @@ &:hover, &:active, &:focus { - color: $gl-link-color; + color: $blue-600; text-decoration: none; } } @@ -445,7 +445,7 @@ &:hover, &:active, &:focus { - color: $gl-link-color; + color: $blue-600; text-decoration: none; } } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index af17210f341..268e68dbb15 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -114,7 +114,7 @@ hr { .item-title { font-weight: $gl-font-weight-bold; } .author-link { - color: $gl-link-color; + color: $blue-600; } .back-link { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 00eac1688f2..54882633fea 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -286,19 +286,19 @@ span.idiff { .new-file { a { - color: $gl-text-green; + color: $green-600; } } .renamed-file { a { - color: $gl-text-orange; + color: $orange-600; } } .deleted-file { a { - color: $gl-text-red; + color: $red-500; } } diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index e4bcb92876d..7a4c3914fb0 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -16,10 +16,10 @@ color: $gl-text-color; a { - color: $gl-link-color; + color: $blue-600; &:hover { - color: $gl-link-hover-color; + color: $blue-800; text-decoration: none; } } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 437fcff5c62..a70eece8f68 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -170,7 +170,7 @@ label { } .form-control::-webkit-input-placeholder { - color: $placeholder-text-color; + color: $gl-text-color-tertiary; } .input-group { diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 7290a174668..d8391b59a8c 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -179,7 +179,7 @@ &:hover, &:focus { svg { - fill: $gl-link-color; + fill: $blue-600; } } } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 473ca408c04..eccc814b747 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -180,7 +180,7 @@ } a > code { - color: $gl-link-color; + color: $blue-600; } dd { @@ -423,25 +423,25 @@ h4 { input, textarea { &::-webkit-input-placeholder { - color: $placeholder-text-color; + color: $gl-text-color-tertiary; } // support firefox 19+ vendor prefix &::-moz-placeholder { - color: $placeholder-text-color; + color: $gl-text-color-tertiary; opacity: 1; // FF defaults to 0.54 } // scss-lint:disable PseudoElement // support Edge vendor prefix &::-ms-input-placeholder { - color: $placeholder-text-color; + color: $gl-text-color-tertiary; } // scss-lint:disable PseudoElement // support IE vendor prefix &:-ms-input-placeholder { - color: $placeholder-text-color; + color: $gl-text-color-tertiary; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index d2ea314f176..866cb88ba5b 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -195,19 +195,10 @@ $gl-text-color-quaternary: #d6d6d6; $gl-text-color-inverted: rgba(255, 255, 255, 1); $gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85); $gl-text-color-disabled: #919191; -$gl-text-green: $green-600; -$gl-text-green-hover: $green-700; -$gl-text-red: $red-500; -$gl-text-orange: $orange-600; -$gl-link-color: $blue-600; -$gl-link-hover-color: $blue-800; $gl-grayish-blue: #7f8fa4; -$gl-gray: $gl-text-color; $gl-gray-dark: #313236; $gl-gray-light: #5c5c5c; $gl-header-color: #4c4e54; -$gl-header-nav-hover-color: #434343; -$placeholder-text-color: $gl-text-color-tertiary; /* * Lists @@ -226,7 +217,7 @@ $list-warning-row-color: $orange-700; /* * Markdown */ -$md-link-color: $gl-link-color; +$md-link-color: $blue-600; $md-area-border: #ddd; /* @@ -583,8 +574,8 @@ $commit-message-text-area-bg: rgba(0, 0, 0, 0); $common-gray: $gl-text-color; $common-gray-light: #bbb; $common-gray-dark: #444; -$common-red: $gl-text-red; -$common-green: $gl-text-green; +$common-red: $red-500; +$common-green: $green-600; /* * Editor diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss index dbd3144b9b4..f2d296fb875 100644 --- a/app/assets/stylesheets/framework/zen.scss +++ b/app/assets/stylesheets/framework/zen.scss @@ -44,7 +44,7 @@ color: $gl-text-color-secondary; &:hover { - color: $gl-link-color; + color: $blue-600; text-decoration: none; } } diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 2b8163b8c68..af91497d0ea 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -1158,7 +1158,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; } a { - color: $gl-link-color; + color: $blue-600; } } diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index a68b47b1d02..91f470ca709 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -225,7 +225,7 @@ outline: 0; &:hover { - color: $gl-link-color; + color: $blue-600; } } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index e8158cd7f6b..1696d18584d 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -221,7 +221,7 @@ color: $gl-text-color; &:hover { - color: $gl-link-color; + color: $blue-600; text-decoration: none; } } diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index bce83bf0dd0..10764e0f3df 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -279,7 +279,7 @@ } &.autodevops-link { - color: $gl-link-color; + color: $blue-600; } } @@ -321,7 +321,7 @@ } .commit-sha { - color: $gl-link-color; + color: $blue-600; } .commit-row-message { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index e2c0a7a6225..bba9f38d3dd 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -360,7 +360,7 @@ } .commit-sha { - color: $gl-link-color; + color: $blue-600; line-height: 1.3; vertical-align: top; font-weight: $gl-font-weight-normal; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 591e21243ed..47778110bae 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -511,13 +511,13 @@ padding: 0; background-color: transparent; border: 0; - color: $gl-link-color; + color: $blue-600; font-weight: $gl-font-weight-bold; &:hover, &:focus { outline: none; - color: $gl-link-hover-color; + color: $blue-800; } .caret-icon { diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 8a074017344..179c0964567 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -479,10 +479,10 @@ .deploy-info-text-link { font-family: $monospace-font; - fill: $gl-link-color; + fill: $blue-600; &:hover { - fill: $gl-link-hover-color; + fill: $blue-800; } } diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss index 49d8a5d959b..22fce893fd7 100644 --- a/app/assets/stylesheets/pages/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss @@ -24,11 +24,11 @@ } .graph-additions { - color: $gl-text-green; + color: $green-600; } .graph-deletions { - color: $gl-text-red; + color: $red-500; } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 8e78d9f65eb..d16a63d009a 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -141,7 +141,7 @@ color: inherit; &:hover { - color: $gl-link-hover-color; + color: $blue-800; .avatar { border-color: rgba($avatar-border, .2); @@ -241,7 +241,7 @@ &:hover { text-decoration: underline; - color: $gl-link-hover-color; + color: $blue-800; } } } @@ -329,7 +329,7 @@ } .btn-secondary-hover-link:hover { - color: $gl-link-color; + color: $blue-600; } .sidebar-collapsed-icon { @@ -448,8 +448,8 @@ } .todo-undone { - color: $gl-link-color; - fill: $gl-link-color; + color: $blue-600; + fill: $blue-600; } .author { diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 212e5979273..0f95fb911e1 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -157,7 +157,7 @@ ul.related-merge-requests > li { .issuable-email-modal-btn { padding: 0; - color: $gl-link-color; + color: $blue-600; background-color: transparent; border: 0; outline: 0; @@ -190,7 +190,7 @@ ul.related-merge-requests > li { .create-mr-dropdown-wrap { .ref::selection { - color: $placeholder-text-color; + color: $gl-text-color-tertiary; } .dropdown { diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index b25dc4f419a..d32943fceec 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -114,7 +114,7 @@ } &:hover { - color: $gl-link-color; + color: $blue-600; &.remove-row { color: $gl-danger; @@ -343,10 +343,10 @@ &.remove-row { &:hover { - color: $gl-text-red; + color: $red-500; svg { - fill: $gl-text-red; + fill: $red-500; } } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 46437ce5841..1e92582d6d9 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -30,7 +30,7 @@ .milestone-progress { a { - color: $gl-link-color; + color: $blue-600; } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 8acd64ca1a1..4f861d43f55 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -306,7 +306,7 @@ &:hover, &:focus { - color: $gl-link-color; + color: $blue-600; outline: 0; } @@ -424,7 +424,7 @@ .uploading-error-icon, .uploading-error-message { - color: $gl-text-red; + color: $red-500; } .uploading-error-message { @@ -443,7 +443,7 @@ .attach-new-file, .button-attach-file, .retry-uploading-link { - color: $gl-link-color; + color: $blue-600; padding: 0; background: none; border: 0; @@ -452,5 +452,5 @@ } .markdown-selector { - color: $gl-link-color; + color: $blue-600; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index c369d89d63c..8d28daac750 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -210,7 +210,7 @@ ul.notes { } a { - color: $gl-link-color; + color: $blue-600; } p { @@ -253,14 +253,14 @@ ul.notes { overflow: hidden; .system-note-commit-list-toggler { - color: $gl-link-color; + color: $blue-600; padding: 10px 0 0; cursor: pointer; position: relative; z-index: 2; &:hover { - color: $gl-link-color; + color: $blue-600; text-decoration: underline; } } @@ -390,7 +390,7 @@ ul.notes { color: inherit; &:hover { - color: $gl-link-color; + color: $blue-600; } &:focus, @@ -451,7 +451,7 @@ ul.notes { .discussion-headline-light { a { - color: $gl-link-color; + color: $blue-600; } } @@ -560,12 +560,12 @@ ul.notes { &:hover, &.is-active { .danger-highlight { - color: $gl-text-red; + color: $red-500; } .link-highlight { - color: $gl-link-color; - fill: $gl-link-color; + color: $blue-600; + fill: $blue-600; } .award-control-icon-neutral { @@ -597,13 +597,13 @@ ul.notes { transition: color 0.1s linear; &:hover { - color: $gl-link-color; + color: $blue-600; } &:focus { text-decoration: underline; outline: none; - color: $gl-link-color; + color: $blue-600; } .fa { @@ -673,7 +673,7 @@ ul.notes { } a { - color: $gl-link-color; + color: $blue-600; } } @@ -759,16 +759,16 @@ ul.notes { &:not(.is-disabled) { &:hover, &:focus { - color: $gl-text-green; + color: $green-600; } } &.is-active { - color: $gl-text-green; + color: $green-600; &:hover, &:focus { - color: $gl-text-green-hover; + color: $green-700; } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index b68c89c25d8..ad057ed3c83 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -175,7 +175,7 @@ } .commit-sha { - color: $gl-link-color; + color: $blue-600; } .badge { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 6d9f415e869..fffb440027c 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -388,7 +388,7 @@ line-height: $gl-btn-line-height; &:hover { - color: $gl-link-color; + color: $blue-600; } } } @@ -961,7 +961,7 @@ pre.light-well { margin-left: 5px; &.is-done { - color: $gl-text-green; + color: $green-600; } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index c9405004c38..5b3a468cd1c 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -259,6 +259,6 @@ input[type='checkbox']:hover { &:hover, &:focus { - color: $gl-link-color; + color: $blue-600; } } -- cgit v1.2.1 From 8650d72eb271417dd3bb11fee185da2fdacb7358 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 14 Aug 2018 19:22:18 +0100 Subject: regenerate .pot --- locale/gitlab.pot | 3 --- 1 file changed, 3 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4c660ae45c1..04141584cac 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5321,9 +5321,6 @@ msgstr "" msgid "The time taken by each data entry gathered by that stage." msgstr "" -msgid "The update action will time out after 15 minutes. For big repositories, use a clone/push combination." -msgstr "" - msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of :. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side." msgstr "" -- cgit v1.2.1 From 00c98303a1adc1e5f8af04415b4eb4c89b3b1386 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 14 Aug 2018 13:33:49 -0500 Subject: Add author-link style from common.scss in EE --- app/assets/stylesheets/framework/common.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 268e68dbb15..48a87ea8616 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -117,6 +117,10 @@ hr { color: $blue-600; } +.author-link:hover { + text-decoration: none; +} + .back-link { font-size: 14px; } -- cgit v1.2.1 From 6a3437ff4d4080a86e4c6814313c77ef60ecf0c6 Mon Sep 17 00:00:00 2001 From: Jens Goldhammer Date: Tue, 14 Aug 2018 18:55:55 +0000 Subject: chore(docs): add field repository_update_events - adds the field repository_update_events as json field --- doc/api/system_hooks.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index dd424470b67..7b8db6cfa8f 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -34,6 +34,7 @@ Example response: "push_events":true, "tag_push_events":false, "merge_requests_events": true, + "repository_update_events": true, "enable_ssl_verification":true } ] @@ -56,6 +57,7 @@ POST /hooks | `push_events` | boolean | no | When true, the hook will fire on push events | | `tag_push_events` | boolean | no | When true, the hook will fire on new tags being pushed | | `merge_requests_events` | boolean | no | Trigger hook on merge requests events | +| `repository_update_events` | boolean | no | Trigger hook on repository update events | | `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook | Example request: @@ -75,6 +77,7 @@ Example response: "push_events":true, "tag_push_events":false, "merge_requests_events": true, + "repository_update_events": true, "enable_ssl_verification":true } ] @@ -127,4 +130,4 @@ Example request: ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks/2 -``` +``` \ No newline at end of file -- cgit v1.2.1 From b4e682eedd6b58f3f245fc1450675909fa0fa5de Mon Sep 17 00:00:00 2001 From: Jan Beckmann Date: Tue, 14 Aug 2018 21:12:07 +0200 Subject: Fix missing Google icon in audit log Fixes #50180 --- app/helpers/icons_helper.rb | 2 ++ changelogs/unreleased/50180-fa-icon-google-audit.yml | 5 +++++ spec/helpers/icons_helper_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 changelogs/unreleased/50180-fa-icon-google-audit.yml diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 41084ec686f..a8a10c98d69 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -62,6 +62,8 @@ module IconsHelper names = "key" when "two-factor" names = "key" + when "google_oauth2" + names = "google" end options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options) diff --git a/changelogs/unreleased/50180-fa-icon-google-audit.yml b/changelogs/unreleased/50180-fa-icon-google-audit.yml new file mode 100644 index 00000000000..709e769eca1 --- /dev/null +++ b/changelogs/unreleased/50180-fa-icon-google-audit.yml @@ -0,0 +1,5 @@ +--- +title: Show google icon in audit log +merge_request: +author: Jan Beckmann +type: fixed diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index 82f588d1a08..e59f9a7bbe2 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -80,6 +80,26 @@ describe IconsHelper do end end + describe 'audit icon' do + it 'returns right icon name for standard auth' do + icon_name = 'standard' + expect(audit_icon(icon_name).to_s) + .to eq '' + end + + it 'returns right icon name for two-factor auth' do + icon_name = 'two-factor' + expect(audit_icon(icon_name).to_s) + .to eq '' + end + + it 'returns right icon name for google_oauth2 auth' do + icon_name = 'google_oauth2' + expect(audit_icon(icon_name).to_s) + .to eq '' + end + end + describe 'file_type_icon_class' do it 'returns folder class' do expect(file_type_icon_class('folder', 0, 'folder_name')).to eq 'folder' -- cgit v1.2.1 From 50a231787d87c59bb617d902638f4d8a4448d74e Mon Sep 17 00:00:00 2001 From: Jan Beckmann Date: Tue, 14 Aug 2018 21:44:16 +0200 Subject: Add merge request number to changelog --- changelogs/unreleased/50180-fa-icon-google-audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/50180-fa-icon-google-audit.yml b/changelogs/unreleased/50180-fa-icon-google-audit.yml index 709e769eca1..fb1771a7570 100644 --- a/changelogs/unreleased/50180-fa-icon-google-audit.yml +++ b/changelogs/unreleased/50180-fa-icon-google-audit.yml @@ -1,5 +1,5 @@ --- title: Show google icon in audit log -merge_request: +merge_request: 21207 author: Jan Beckmann type: fixed -- cgit v1.2.1 From c0d1d46134f900306853b33ad30d2fbc5bbf7522 Mon Sep 17 00:00:00 2001 From: Jan Beckmann Date: Tue, 14 Aug 2018 22:55:32 +0200 Subject: Fix rubocop offense --- spec/helpers/icons_helper_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index e59f9a7bbe2..4b40d523287 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -85,12 +85,12 @@ describe IconsHelper do icon_name = 'standard' expect(audit_icon(icon_name).to_s) .to eq '' - end + end it 'returns right icon name for two-factor auth' do icon_name = 'two-factor' expect(audit_icon(icon_name).to_s) - .to eq '' + .to eq '' end it 'returns right icon name for google_oauth2 auth' do -- cgit v1.2.1 From 7b5409d58b207c33527359bf55200cf576d76b35 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Tue, 14 Aug 2018 23:10:05 +1200 Subject: Vendor in Auto-DevOps.gitlab-ci.yml to update glibc to 2.28 vendored from https://gitlab.com/gitlab-org/gitlab-ci-yml/merge_requests/186 Thanks @sgerrand ! --- changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml | 5 +++++ vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml diff --git a/changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml b/changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml new file mode 100644 index 00000000000..a1625193189 --- /dev/null +++ b/changelogs/unreleased/auto-devops-gitlab-ci-glic-228.yml @@ -0,0 +1,5 @@ +--- +title: 'Auto-DevOps.gitlab-ci.yml: update glibc package to 2.28' +merge_request: 21191 +author: sgerrand +type: fixed diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 39876805ffa..ffcf5648075 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -642,9 +642,9 @@ rollout 100%: function install_dependencies() { apk add -U openssl curl tar gzip bash ca-certificates git wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk - apk add glibc-2.23-r3.apk - rm glibc-2.23-r3.apk + wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk + apk add glibc-2.28-r0.apk + rm glibc-2.28-r0.apk curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx mv linux-amd64/helm /usr/bin/ -- cgit v1.2.1 From ffd164d27f674b554fdffbffa828a9715c93ee60 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 14 Aug 2018 22:28:46 +0100 Subject: Fix bugs in Gitlab::Template::Finders preventing instances from BaseTemplate.all from loading content --- .../template/finders/base_template_finder.rb | 2 +- .../template/finders/repo_template_finder.rb | 10 +++--- .../template/finders/repo_template_finders_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 spec/lib/gitlab/template/finders/repo_template_finders_spec.rb 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/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 -- cgit v1.2.1 From 6688d800ca45ae9352f95972e71431de3232fa5f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 14 Aug 2018 23:07:58 +0100 Subject: Move cohorts and conv dev index js to new pages location --- app/assets/javascripts/pages/admin/cohorts/index.js | 3 --- app/assets/javascripts/pages/admin/cohorts/usage_ping.js | 13 ------------- .../admin/conversational_development_index/show/index.js | 3 --- .../javascripts/pages/instance_statistics/cohorts/index.js | 3 +++ .../pages/instance_statistics/cohorts/usage_ping.js | 13 +++++++++++++ .../conversational_development_index/show/index.js | 3 +++ 6 files changed, 19 insertions(+), 19 deletions(-) delete mode 100644 app/assets/javascripts/pages/admin/cohorts/index.js delete mode 100644 app/assets/javascripts/pages/admin/cohorts/usage_ping.js delete mode 100644 app/assets/javascripts/pages/admin/conversational_development_index/show/index.js create mode 100644 app/assets/javascripts/pages/instance_statistics/cohorts/index.js create mode 100644 app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js create mode 100644 app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js diff --git a/app/assets/javascripts/pages/admin/cohorts/index.js b/app/assets/javascripts/pages/admin/cohorts/index.js deleted file mode 100644 index 2d5020dbef4..00000000000 --- a/app/assets/javascripts/pages/admin/cohorts/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import initUsagePing from './usage_ping'; - -document.addEventListener('DOMContentLoaded', initUsagePing); diff --git a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js deleted file mode 100644 index 914a9661c27..00000000000 --- a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js +++ /dev/null @@ -1,13 +0,0 @@ -import axios from '../../../lib/utils/axios_utils'; -import { __ } from '../../../locale'; -import flash from '../../../flash'; - -export default function UsagePing() { - const el = document.querySelector('.usage-data'); - - axios.get(el.dataset.endpoint, { - responseType: 'text', - }).then(({ data }) => { - el.innerHTML = data; - }).catch(() => flash(__('Error fetching usage ping data.'))); -} diff --git a/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js b/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js deleted file mode 100644 index c1056537f90..00000000000 --- a/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import UserCallout from '~/user_callout'; - -document.addEventListener('DOMContentLoaded', () => new UserCallout()); diff --git a/app/assets/javascripts/pages/instance_statistics/cohorts/index.js b/app/assets/javascripts/pages/instance_statistics/cohorts/index.js new file mode 100644 index 00000000000..2d5020dbef4 --- /dev/null +++ b/app/assets/javascripts/pages/instance_statistics/cohorts/index.js @@ -0,0 +1,3 @@ +import initUsagePing from './usage_ping'; + +document.addEventListener('DOMContentLoaded', initUsagePing); diff --git a/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js b/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js new file mode 100644 index 00000000000..914a9661c27 --- /dev/null +++ b/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js @@ -0,0 +1,13 @@ +import axios from '../../../lib/utils/axios_utils'; +import { __ } from '../../../locale'; +import flash from '../../../flash'; + +export default function UsagePing() { + const el = document.querySelector('.usage-data'); + + axios.get(el.dataset.endpoint, { + responseType: 'text', + }).then(({ data }) => { + el.innerHTML = data; + }).catch(() => flash(__('Error fetching usage ping data.'))); +} diff --git a/app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js new file mode 100644 index 00000000000..c1056537f90 --- /dev/null +++ b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js @@ -0,0 +1,3 @@ +import UserCallout from '~/user_callout'; + +document.addEventListener('DOMContentLoaded', () => new UserCallout()); -- cgit v1.2.1 From 401bcf043cb298fd074f94719dec263af2823bbd Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 14 Aug 2018 23:18:27 +0100 Subject: Rename conv dev index show action to index action in js pages dir --- .../instance_statistics/conversational_development_index/index.js | 3 +++ .../instance_statistics/conversational_development_index/show/index.js | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js delete mode 100644 app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js diff --git a/app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js new file mode 100644 index 00000000000..c1056537f90 --- /dev/null +++ b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js @@ -0,0 +1,3 @@ +import UserCallout from '~/user_callout'; + +document.addEventListener('DOMContentLoaded', () => new UserCallout()); diff --git a/app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js deleted file mode 100644 index c1056537f90..00000000000 --- a/app/assets/javascripts/pages/instance_statistics/conversational_development_index/show/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import UserCallout from '~/user_callout'; - -document.addEventListener('DOMContentLoaded', () => new UserCallout()); -- cgit v1.2.1 From 2cfa4f4888dd753d62a3f42cd02860828f1b0f56 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 14 Aug 2018 23:51:24 +0100 Subject: Add test for usage ping js in cohorts_spec --- spec/features/instance_statistics/cohorts_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/features/instance_statistics/cohorts_spec.rb b/spec/features/instance_statistics/cohorts_spec.rb index 81fc5eff980..573f8600be1 100644 --- a/spec/features/instance_statistics/cohorts_spec.rb +++ b/spec/features/instance_statistics/cohorts_spec.rb @@ -12,4 +12,12 @@ describe 'Cohorts page' do expect(page).to have_content("#{Time.now.strftime('%b %Y')} 3 0") end + + it 'shows usage data', :js do + visit instance_statistics_cohorts_path + + wait_for_requests + + expect(find('.js-syntax-highlight').text).not_to eq('') + end end -- cgit v1.2.1 From 02fda22f19b21666323df545d3f817a7f66a1b6a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 14 Aug 2018 23:57:21 +0100 Subject: add test for convdev index user callout --- .../conversational_development_index_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/conversational_development_index_spec.rb index d441a7a5af9..a6c16b6a2a3 100644 --- a/spec/features/instance_statistics/conversational_development_index_spec.rb +++ b/spec/features/instance_statistics/conversational_development_index_spec.rb @@ -5,6 +5,16 @@ describe 'Conversational Development Index' do sign_in(create(:admin)) end + it 'has dismissable intro callout', :js do + visit instance_statistics_conversational_development_index_index_path + + expect(page).to have_content 'Introducing Your Conversational Development Index' + + find('.js-close-callout').click + + expect(page).not_to have_content 'Introducing Your Conversational Development Index' + end + context 'when usage ping is disabled' do it 'shows empty state' do stub_application_setting(usage_ping_enabled: false) -- cgit v1.2.1 From 18d1af847e2ae348e8bd6a6b5587dea6d3b1c37a Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Wed, 8 Aug 2018 22:02:59 -0500 Subject: Combines emoji award spec files into single user_interacts_with_awards_in_issue_spec Closes: #45238 --- ...8rzz-consolidate-specs-testing-emoji-awards.yml | 6 + spec/features/issues/award_emoji_spec.rb | 146 --------- spec/features/issues/award_spec.rb | 51 --- .../issues/user_interacts_with_awards_spec.rb | 347 +++++++++++++++++++++ .../user_interacts_with_awards_in_issue_spec.rb | 172 ---------- 5 files changed, 353 insertions(+), 369 deletions(-) create mode 100644 changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml delete mode 100644 spec/features/issues/award_emoji_spec.rb delete mode 100644 spec/features/issues/award_spec.rb create mode 100644 spec/features/issues/user_interacts_with_awards_spec.rb delete mode 100644 spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb diff --git a/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml b/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml new file mode 100644 index 00000000000..bcf3d2c8e16 --- /dev/null +++ b/changelogs/unreleased/n8rzz-consolidate-specs-testing-emoji-awards.yml @@ -0,0 +1,6 @@ +--- +title: Combines emoji award spec files into single user_interacts_with_awards_in_issue_spec.rb + file +merge_request: 21126 +author: Nate Geslin +type: other diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb deleted file mode 100644 index bf60b18873c..00000000000 --- a/spec/features/issues/award_emoji_spec.rb +++ /dev/null @@ -1,146 +0,0 @@ -require 'rails_helper' - -describe 'Awards Emoji' do - let!(:project) { create(:project, :public) } - let!(:user) { create(:user) } - let(:issue) do - create(:issue, - assignees: [user], - project: project) - end - - context 'authorized user' do - before do - project.add_maintainer(user) - sign_in(user) - end - - describe 'visiting an issue with a legacy award emoji that is not valid anymore' do - before do - # The `heart_tip` emoji is not valid anymore so we need to skip validation - issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false) - visit project_issue_path(project, issue) - wait_for_requests - end - - # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529 - it 'does not shows a 500 page', :js do - expect(page).to have_text(issue.title) - end - end - - describe 'Click award emoji from issue#show' do - let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } - - before do - visit project_issue_path(project, issue) - wait_for_requests - end - - it 'increments the thumbsdown emoji', :js do - find('[data-name="thumbsdown"]').click - wait_for_requests - expect(thumbsdown_emoji).to have_text("1") - end - - context 'click the thumbsup emoji' do - it 'increments the thumbsup emoji', :js do - find('[data-name="thumbsup"]').click - wait_for_requests - expect(thumbsup_emoji).to have_text("1") - end - - it 'decrements the thumbsdown emoji', :js do - expect(thumbsdown_emoji).to have_text("0") - end - end - - context 'click the thumbsdown emoji' do - it 'increments the thumbsdown emoji', :js do - find('[data-name="thumbsdown"]').click - wait_for_requests - expect(thumbsdown_emoji).to have_text("1") - end - - it 'decrements the thumbsup emoji', :js do - expect(thumbsup_emoji).to have_text("0") - end - end - - it 'toggles the smiley emoji on a note', :js do - toggle_smiley_emoji(true) - - within('.note-body') do - expect(find(emoji_counter)).to have_text("1") - end - - toggle_smiley_emoji(false) - - within('.note-body') do - expect(page).not_to have_selector(emoji_counter) - end - end - - context 'execute /award quick action' do - it 'toggles the emoji award on noteable', :js do - execute_quick_action('/award :100:') - - expect(find(noteable_award_counter)).to have_text("1") - - execute_quick_action('/award :100:') - - expect(page).not_to have_selector(noteable_award_counter) - end - end - end - end - - context 'unauthorized user', :js do - before do - visit project_issue_path(project, issue) - end - - it 'has disabled emoji button' do - expect(first('.award-control')[:class]).to have_text('disabled') - end - end - - def execute_quick_action(cmd) - within('.js-main-target-form') do - fill_in 'note[note]', with: cmd - click_button 'Comment' - end - - wait_for_requests - end - - def thumbsup_emoji - page.all(emoji_counter).first - end - - def thumbsdown_emoji - page.all(emoji_counter).last - end - - def emoji_counter - 'span.js-counter' - end - - def noteable_award_counter - ".awards .active" - end - - def toggle_smiley_emoji(status) - within('.note') do - find('.note-emoji-button').click - end - - unless status - first('[data-name="smiley"]').click - else - find('[data-name="smiley"]').click - end - - wait_for_requests - end -end diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb deleted file mode 100644 index e53a4ce49c7..00000000000 --- a/spec/features/issues/award_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'rails_helper' - -describe 'Issue awards', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - let(:issue) { create(:issue, project: project) } - - describe 'logged in' do - before do - sign_in(user) - visit project_issue_path(project, issue) - wait_for_requests - end - - it 'adds award to issue' do - first('.js-emoji-btn').click - expect(page).to have_selector('.js-emoji-btn.active') - expect(first('.js-emoji-btn')).to have_content '1' - - visit project_issue_path(project, issue) - expect(first('.js-emoji-btn')).to have_content '1' - end - - it 'removes award from issue' do - first('.js-emoji-btn').click - find('.js-emoji-btn.active').click - expect(first('.js-emoji-btn')).to have_content '0' - - visit project_issue_path(project, issue) - expect(first('.js-emoji-btn')).to have_content '0' - end - - it 'only has one menu on the page' do - first('.js-add-award').click - expect(page).to have_selector('.emoji-menu') - - expect(page).to have_selector('.emoji-menu', count: 1) - end - end - - describe 'logged out' do - before do - visit project_issue_path(project, issue) - wait_for_requests - end - - it 'does not see award menu button' do - expect(page).not_to have_selector('.js-award-holder') - end - end -end diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb new file mode 100644 index 00000000000..afa425c2cec --- /dev/null +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -0,0 +1,347 @@ +require 'spec_helper' + +describe 'User interacts with awards' do + let(:user) { create(:user) } + + describe 'User interacts with awards in an issue', :js do + let(:issue) { create(:issue, project: project)} + let(:project) { create(:project) } + + before do + project.add_maintainer(user) + sign_in(user) + + visit(project_issue_path(project, issue)) + end + + it 'toggles the thumbsup award emoji' do + page.within('.awards') do + thumbsup = page.first('.award-control') + thumbsup.click + thumbsup.hover + + expect(page).to have_selector('.js-emoji-btn') + expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") + expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') + + thumbsup = page.first('.award-control') + thumbsup.click + thumbsup.hover + + expect(page).to have_selector('.award-control.js-emoji-btn') + expect(page.all('.award-control.js-emoji-btn').size).to eq(2) + + page.all('.award-control.js-emoji-btn').each do |element| + expect(element['title']).to eq('') + end + + expect(page.all('.award-control .js-counter')).to all(have_content('0')) + + thumbsup = page.first('.award-control') + thumbsup.click + thumbsup.hover + + expect(page).to have_selector('.js-emoji-btn') + expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") + expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') + end + end + + it 'toggles a custom award emoji' do + page.within('.awards') do + page.find('.js-add-award').click + end + + page.find('.emoji-menu.is-visible') + + expect(page).to have_selector('.js-emoji-menu-search') + expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) + + page.within('.emoji-menu-content') do + emoji_button = page.first('.js-emoji-btn') + emoji_button.hover + emoji_button.click + end + + page.within('.awards') do + expect(page).to have_selector('.js-emoji-btn') + expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') + expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") + + expect do + page.find('.js-emoji-btn.active').click + wait_for_requests + end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2) + end + end + + it 'shows the list of award emoji categories' do + page.within('.awards') do + page.find('.js-add-award').click + end + + page.find('.emoji-menu.is-visible') + + expect(page).to have_selector('.js-emoji-menu-search') + expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) + + fill_in('emoji-menu-search', with: 'hand') + + page.within('.emoji-menu-content') do + expect(page).to have_selector('[data-name="raised_hand"]') + end + end + + it 'adds an award emoji by a comment' do + page.within('.js-main-target-form') do + fill_in('note[note]', with: ':smile:') + + click_button('Comment') + end + + expect(page).to have_emoji('smile') + end + + context 'when a project is archived' do + let(:project) { create(:project, :archived) } + + it 'hides the add award button' do + page.within('.awards') do + expect(page).not_to have_css('.js-add-award') + end + end + end + + context 'User interacts with awards on a note' do + let!(:note) { create(:note, noteable: issue, project: issue.project) } + let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') } + + it 'shows the award on the note' do + page.within('.note-awards') do + expect(page).to have_emoji('100') + end + end + + it 'allows adding a vote to an award' do + page.within('.note-awards') do + find('gl-emoji[data-name="100"]').click + end + wait_for_requests + + expect(note.reload.award_emoji.size).to eq(2) + end + + it 'allows adding a new emoji' do + page.within('.note-actions') do + find('a.js-add-award').click + end + page.within('.emoji-menu-content') do + find('gl-emoji[data-name="8ball"]').click + end + wait_for_requests + + page.within('.note-awards') do + expect(page).to have_emoji('8ball') + end + expect(note.reload.award_emoji.size).to eq(2) + end + + context 'when the project is archived' do + let(:project) { create(:project, :archived) } + + it 'hides the buttons for adding new emoji' do + page.within('.note-awards') do + expect(page).not_to have_css('.award-menu-holder') + end + + page.within('.note-actions') do + expect(page).not_to have_css('a.js-add-award') + end + end + + it 'does not allow toggling existing emoji' do + page.within('.note-awards') do + find('gl-emoji[data-name="100"]').click + end + wait_for_requests + + expect(note.reload.award_emoji.size).to eq(1) + end + end + end + end + + describe 'User interacts with awards on an issue', :js do + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + + describe 'logged in' do + before do + sign_in(user) + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'adds award to issue' do + first('.js-emoji-btn').click + + expect(page).to have_selector('.js-emoji-btn.active') + expect(first('.js-emoji-btn')).to have_content '1' + + visit project_issue_path(project, issue) + + expect(first('.js-emoji-btn')).to have_content '1' + end + + it 'removes award from issue' do + first('.js-emoji-btn').click + find('.js-emoji-btn.active').click + + expect(first('.js-emoji-btn')).to have_content '0' + + visit project_issue_path(project, issue) + + expect(first('.js-emoji-btn')).to have_content '0' + end + + it 'only has one menu on the page' do + first('.js-add-award').click + + expect(page).to have_selector('.emoji-menu', count: 1) + end + end + + describe 'logged out' do + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'does not see award menu button' do + expect(page).not_to have_selector('.js-award-holder') + end + end + end + + describe 'Awards Emoji' do + let!(:project) { create(:project, :public) } + let(:issue) { create(:issue, assignees: [user], project: project) } + + context 'authorized user' do + before do + project.add_maintainer(user) + sign_in(user) + end + + describe 'visiting an issue with a legacy award emoji that is not valid anymore' do + before do + # The `heart_tip` emoji is not valid anymore so we need to skip validation + issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false) + visit project_issue_path(project, issue) + wait_for_requests + end + + # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529 + it 'does not shows a 500 page', :js do + expect(page).to have_text(issue.title) + end + end + + describe 'Click award emoji from issue#show' do + let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } + + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + context 'click the thumbsdown emoji' do + it 'increments the thumbsdown emoji', :js do + find('[data-name="thumbsdown"]').click + wait_for_requests + expect(thumbsdown_emoji).to have_text("1") + end + + it 'decrements the thumbsup emoji', :js do + expect(thumbsup_emoji).to have_text("0") + end + end + + it 'toggles the smiley emoji on a note', :js do + toggle_smiley_emoji(true) + + within('.note-body') do + expect(find(emoji_counter)).to have_text("1") + end + + toggle_smiley_emoji(false) + + within('.note-body') do + expect(page).not_to have_selector(emoji_counter) + end + end + + context 'execute /award quick action' do + it 'toggles the emoji award on noteable', :js do + execute_quick_action('/award :100:') + + expect(find(noteable_award_counter)).to have_text("1") + + execute_quick_action('/award :100:') + + expect(page).not_to have_selector(noteable_award_counter) + end + end + end + end + + context 'unauthorized user', :js do + before do + visit project_issue_path(project, issue) + end + + it 'has disabled emoji button' do + expect(first('.award-control')[:class]).to have_text('disabled') + end + end + + def execute_quick_action(cmd) + within('.js-main-target-form') do + fill_in 'note[note]', with: cmd + click_button 'Comment' + end + + wait_for_requests + end + + def thumbsup_emoji + page.all(emoji_counter).first + end + + def thumbsdown_emoji + page.all(emoji_counter).last + end + + def emoji_counter + 'span.js-counter' + end + + def noteable_award_counter + ".awards .active" + end + + def toggle_smiley_emoji(status) + within('.note') do + find('.note-emoji-button').click + end + + if !status + first('[data-name="smiley"]').click + else + find('[data-name="smiley"]').click + end + + wait_for_requests + end + end +end diff --git a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb deleted file mode 100644 index 4d860893abe..00000000000 --- a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb +++ /dev/null @@ -1,172 +0,0 @@ -require 'spec_helper' - -describe 'User interacts with awards in an issue', :js do - let(:issue) { create(:issue, project: project)} - let(:project) { create(:project) } - let(:user) { create(:user) } - - before do - project.add_maintainer(user) - sign_in(user) - - visit(project_issue_path(project, issue)) - end - - it 'toggles the thumbsup award emoji' do - page.within('.awards') do - thumbsup = page.first('.award-control') - thumbsup.click - thumbsup.hover - - expect(page).to have_selector('.js-emoji-btn') - expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") - expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') - - thumbsup = page.first('.award-control') - thumbsup.click - thumbsup.hover - - expect(page).to have_selector('.award-control.js-emoji-btn') - expect(page.all('.award-control.js-emoji-btn').size).to eq(2) - - page.all('.award-control.js-emoji-btn').each do |element| - expect(element['title']).to eq('') - end - - page.all('.award-control .js-counter').each do |element| - expect(element).to have_content('0') - end - - thumbsup = page.first('.award-control') - thumbsup.click - thumbsup.hover - - expect(page).to have_selector('.js-emoji-btn') - expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") - expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') - end - end - - it 'toggles a custom award emoji' do - page.within('.awards') do - page.find('.js-add-award').click - end - - page.find('.emoji-menu.is-visible') - - expect(page).to have_selector('.js-emoji-menu-search') - expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) - - page.within('.emoji-menu-content') do - emoji_button = page.first('.js-emoji-btn') - emoji_button.hover - emoji_button.click - end - - page.within('.awards') do - expect(page).to have_selector('.js-emoji-btn') - expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') - expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") - - expect do - page.find('.js-emoji-btn.active').click - wait_for_requests - end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2) - end - end - - it 'shows the list of award emoji categories' do - page.within('.awards') do - page.find('.js-add-award').click - end - - page.find('.emoji-menu.is-visible') - - expect(page).to have_selector('.js-emoji-menu-search') - expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) - - fill_in('emoji-menu-search', with: 'hand') - - page.within('.emoji-menu-content') do - expect(page).to have_selector('[data-name="raised_hand"]') - end - end - - it 'adds an award emoji by a comment' do - page.within('.js-main-target-form') do - fill_in('note[note]', with: ':smile:') - - click_button('Comment') - end - - expect(page).to have_emoji('smile') - end - - context 'when a project is archived' do - let(:project) { create(:project, :archived) } - - it 'hides the add award button' do - page.within('.awards') do - expect(page).not_to have_css('.js-add-award') - end - end - end - - context 'awards on a note' do - let!(:note) { create(:note, noteable: issue, project: issue.project) } - let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') } - - it 'shows the award on the note' do - page.within('.note-awards') do - expect(page).to have_emoji('100') - end - end - - it 'allows adding a vote to an award' do - page.within('.note-awards') do - find('gl-emoji[data-name="100"]').click - end - wait_for_requests - - expect(note.reload.award_emoji.size).to eq(2) - end - - it 'allows adding a new emoji' do - page.within('.note-actions') do - find('a.js-add-award').click - end - page.within('.emoji-menu-content') do - find('gl-emoji[data-name="8ball"]').click - end - wait_for_requests - - page.within('.note-awards') do - expect(page).to have_emoji('8ball') - end - expect(note.reload.award_emoji.size).to eq(2) - end - - context 'when the project is archived' do - let(:project) { create(:project, :archived) } - - it 'hides the buttons for adding new emoji' do - page.within('.note-awards') do - expect(page).not_to have_css('.award-menu-holder') - end - - page.within('.note-actions') do - expect(page).not_to have_css('a.js-add-award') - end - end - - it 'does not allow toggling existing emoji' do - page.within('.note-awards') do - find('gl-emoji[data-name="100"]').click - end - wait_for_requests - - expect(note.reload.award_emoji.size).to eq(1) - end - end - end -end -- cgit v1.2.1 From 8a660c79ed635d99dfaaf91980adee429c19f975 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 15 Aug 2018 04:34:24 +0000 Subject: Resolve "Clicking on the text of checkboxes of runner's settings should toggle the checkbox" --- app/views/shared/runners/_form.html.haml | 30 +++++++++++----------- .../unreleased/49905-fix-checkboxes-runners.yml | 5 ++++ locale/gitlab.pot | 30 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 changelogs/unreleased/49905-fix-checkboxes-runners.yml diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml index 0337680d79b..fa93307be31 100644 --- a/app/views/shared/runners/_form.html.haml +++ b/app/views/shared/runners/_form.html.haml @@ -1,56 +1,56 @@ = form_for runner, url: runner_form_url do |f| = form_errors(runner) .form-group.row - = label :active, "Active", class: 'col-form-label col-sm-2' + = label :active, _("Active"), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :active, { class: 'form-check-input' } - %span.light Paused Runners don't accept new jobs + %label.light{ for: :runner_active }= _("Paused Runners don't accept new jobs") .form-group.row - = label :protected, "Protected", class: 'col-form-label col-sm-2' + = label :protected, _("Protected"), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :access_level, { class: 'form-check-input' }, 'ref_protected', 'not_protected' - %span.light This runner will only run on pipelines triggered on protected branches + %label.light{ for: :runner_access_level }= _('This runner will only run on pipelines triggered on protected branches') .form-group.row - = label :run_untagged, 'Run untagged jobs', class: 'col-form-label col-sm-2' + = label :run_untagged, _('Run untagged jobs'), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :run_untagged, { class: 'form-check-input' } - %span.light Indicates whether this runner can pick jobs without tags + %label.light{ for: :runner_run_untagged }= _('Indicates whether this runner can pick jobs without tags') - unless runner.group_type? .form-group.row = label :locked, _('Lock to current projects'), class: 'col-form-label col-sm-2' .col-sm-10 .form-check = f.check_box :locked, { class: 'form-check-input' } - %span.light= _('When a runner is locked, it cannot be assigned to other projects') + %label.light{ for: :runner_locked }= _('When a runner is locked, it cannot be assigned to other projects') .form-group.row = label_tag :token, class: 'col-form-label col-sm-2' do - Token + = _('Token') .col-sm-10 = f.text_field :token, class: 'form-control', readonly: true .form-group.row = label_tag :ip_address, class: 'col-form-label col-sm-2' do - IP Address + = _('IP Address') .col-sm-10 = f.text_field :ip_address, class: 'form-control', readonly: true .form-group.row = label_tag :description, class: 'col-form-label col-sm-2' do - Description + = _('Description') .col-sm-10 = f.text_field :description, class: 'form-control' .form-group.row = label_tag :maximum_timeout_human_readable, class: 'col-form-label col-sm-2' do - Maximum job timeout + = _('Maximum job timeout') .col-sm-10 = f.text_field :maximum_timeout_human_readable, class: 'form-control' - .form-text.text-muted This timeout will take precedence when lower than Project-defined timeout + .form-text.text-muted= _('This timeout will take precedence when lower than Project-defined timeout') .form-group.row = label_tag :tag_list, class: 'col-form-label col-sm-2' do - Tags + = _('Tags') .col-sm-10 = f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control' - .form-text.text-muted You can setup jobs to only use Runners with specific tags. Separate tags with commas. + .form-text.text-muted= _('You can setup jobs to only use Runners with specific tags. Separate tags with commas.') .form-actions - = f.submit 'Save changes', class: 'btn btn-save' + = f.submit _('Save changes'), class: 'btn btn-success' diff --git a/changelogs/unreleased/49905-fix-checkboxes-runners.yml b/changelogs/unreleased/49905-fix-checkboxes-runners.yml new file mode 100644 index 00000000000..af40e5348b8 --- /dev/null +++ b/changelogs/unreleased/49905-fix-checkboxes-runners.yml @@ -0,0 +1,5 @@ +--- +title: Fix checkboxes on runner admin settings - The labels are now clickable +merge_request: +author: +type: fixed diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8f3914c5f69..4edca0c40fe 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2971,6 +2971,9 @@ msgstr "" msgid "IDE|Review" msgstr "" +msgid "IP Address" +msgstr "" + msgid "Identifier" msgstr "" @@ -3061,6 +3064,9 @@ msgstr "" msgid "Incompatible Project" msgstr "" +msgid "Indicates whether this runner can pick jobs without tags" +msgstr "" + msgid "Inline" msgstr "" @@ -3390,6 +3396,9 @@ msgstr "" msgid "Maximum git storage failures" msgstr "" +msgid "Maximum job timeout" +msgstr "" + msgid "May" msgstr "" @@ -3932,6 +3941,9 @@ msgstr "" msgid "Pause" msgstr "" +msgid "Paused Runners don't accept new jobs" +msgstr "" + msgid "Pending" msgstr "" @@ -4424,6 +4436,9 @@ msgstr "" msgid "Promote to group label" msgstr "" +msgid "Protected" +msgstr "" + msgid "Protip:" msgstr "" @@ -4639,6 +4654,9 @@ msgstr "" msgid "Revoke" msgstr "" +msgid "Run untagged jobs" +msgstr "" + msgid "Runner token" msgstr "" @@ -5480,9 +5498,15 @@ msgstr "" msgid "This repository" msgstr "" +msgid "This runner will only run on pipelines triggered on protected branches" +msgstr "" + msgid "This source diff could not be displayed because it is too large." msgstr "" +msgid "This timeout will take precedence when lower than Project-defined timeout" +msgstr "" + msgid "This user has no identities" msgstr "" @@ -5724,6 +5748,9 @@ msgstr "" msgid "ToggleButton|Toggle Status: ON" msgstr "" +msgid "Token" +msgstr "" + msgid "Too many changes to show." msgstr "" @@ -6150,6 +6177,9 @@ msgstr "" msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}" msgstr "" +msgid "You can setup jobs to only use Runners with specific tags. Separate tags with commas." +msgstr "" + msgid "You cannot write to this read-only GitLab instance." msgstr "" -- cgit v1.2.1 From 5ae0b69040fd58f7a8dd04444fd43db263223aec Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 15 Aug 2018 01:22:46 -0500 Subject: add polyfill for String.prototype.includes --- app/assets/javascripts/commons/polyfills.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index 589eeee9695..742cf490ad2 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -8,6 +8,7 @@ import 'core-js/fn/object/assign'; import 'core-js/fn/promise'; import 'core-js/fn/string/code-point-at'; import 'core-js/fn/string/from-code-point'; +import 'core-js/fn/string/includes'; import 'core-js/fn/symbol'; import 'core-js/es6/map'; import 'core-js/es6/weak-map'; -- cgit v1.2.1 From 620fc2808730a9cc1c2b0f532b409cda5fd52460 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 15 Aug 2018 01:29:08 -0500 Subject: add CHANGELOG.md entry for !21214 --- .../unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml diff --git a/changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml b/changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml new file mode 100644 index 00000000000..eb20e34c466 --- /dev/null +++ b/changelogs/unreleased/50281-js-pages-do-not-load-on-windows-8-ie-11.yml @@ -0,0 +1,5 @@ +--- +title: Fix broken JavaScript in IE11 +merge_request: 21214 +author: +type: fixed -- cgit v1.2.1 From d3490f6998b13a6f74af9b6f35a28c115a25143e Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 14 Aug 2018 22:29:59 +0100 Subject: Introduce a LicenseTemplate model and LicenseTemplateFinder helper --- app/finders/license_template_finder.rb | 36 +++++++++++++++++ app/models/license_template.rb | 53 +++++++++++++++++++++++++ spec/finders/license_template_finder_spec.rb | 49 +++++++++++++++++++++++ spec/models/license_template_spec.rb | 59 ++++++++++++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 app/finders/license_template_finder.rb create mode 100644 app/models/license_template.rb create mode 100644 spec/finders/license_template_finder_spec.rb create mode 100644 spec/models/license_template_spec.rb 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/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/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/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 -- cgit v1.2.1 From 40d1fc1cd7180f5ab900ae9cab9fbf1548e35c51 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 14 Aug 2018 22:30:32 +0100 Subject: Convert BlobHelper#licenses_for_select to use the new LicenseTemplateFinder --- app/helpers/blob_helper.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 -- cgit v1.2.1 From f6f6295027a5040f27a8cbb2b979d16b445a284b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 14 Aug 2018 22:31:38 +0100 Subject: Convert the license template API to use the new LicenseTemplateFinder --- lib/api/entities.rb | 2 +- lib/api/templates.rb | 44 ++++++++++++------------------------- spec/requests/api/templates_spec.rb | 3 +++ 3 files changed, 18 insertions(+), 31 deletions(-) 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/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 -- cgit v1.2.1 From 3422cca4b731cf9786822a5971dc1ada308c1b16 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 14 Aug 2018 22:32:24 +0100 Subject: Allow the project_select_tag to specify an initial value and for the selection to be clear-able --- app/assets/javascripts/project_select.js | 8 ++++++++ 1 file changed, 8 insertions(+) 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; -- cgit v1.2.1 From d7be0dc818702b19d44afec575a3423c0d0fed25 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 14 Aug 2018 22:33:14 +0100 Subject: Changes /admin/application_settings to support template repository selection in EE --- app/assets/javascripts/pages/admin/application_settings/index.js | 2 ++ app/views/admin/application_settings/show.html.haml | 2 ++ 2 files changed, 4 insertions(+) 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/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 -- cgit v1.2.1 From 2e8fe26b9bfedf7d65270c9874fcdcb62d58e741 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Wed, 15 Aug 2018 07:27:59 +0000 Subject: Fix `bin/secpick` rainbow gem error, and security branch prefixing --- .gitlab/issue_templates/Security developer workflow.md | 8 ++++---- Gemfile | 2 +- Gemfile.lock | 5 ++--- Gemfile.rails5.lock | 5 ++--- bin/secpick | 4 +++- changelogs/unreleased/mk-bump-rainbow-gem.yml | 5 +++++ 6 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/mk-bump-rainbow-gem.yml diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index c1f702e9385..64b54b171f7 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -12,7 +12,7 @@ Set the title to: `[Security] Description of the original issue` - [ ] Link to the original issue adding it to the [links section](#links) - [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org` - [ ] Create an MR targetting `org` `master`, prefixing your branch with `security-` -- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]` +- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]` - [ ] Add a link to the MR to the [links section](#links) - [ ] Add a link to an EE MR if required - [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**. @@ -22,13 +22,13 @@ Set the title to: `[Security] Description of the original issue` - [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases - [ ] At this point, it might be easy to squash the commits from the MR into one - - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation] + - You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation] - [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable) - [ ] Create each MR targetting the security branch `security-X-Y` - [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR - [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager. -[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script +[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script #### Documentation and final details @@ -68,4 +68,4 @@ Set the title to: `[Security] Description of the original issue` [security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md [RM list]: https://about.gitlab.com/release-managers/ -/label ~security +/label ~security diff --git a/Gemfile b/Gemfile index d9066081f74..5666e6cebc5 100644 --- a/Gemfile +++ b/Gemfile @@ -180,7 +180,7 @@ gem 'rufus-scheduler', '~> 3.4' gem 'httparty', '~> 0.13.3' # Colored output to console -gem 'rainbow', '~> 2.2' +gem 'rainbow', '~> 3.0' # Progress bar gem 'ruby-progressbar' diff --git a/Gemfile.lock b/Gemfile.lock index 62c3b28f386..b33dd75c278 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -691,8 +691,7 @@ GEM activesupport (= 4.2.10) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.2) - rake + rainbow (3.0.0) raindrops (0.18.0) rake (12.3.1) rb-fsevent (0.10.2) @@ -1134,7 +1133,7 @@ DEPENDENCIES rails (= 4.2.10) rails-deprecated_sanitizer (~> 1.0.3) rails-i18n (~> 4.0.9) - rainbow (~> 2.2) + rainbow (~> 3.0) raindrops (~> 0.18) rblineprof (~> 0.3.6) rbtrace (~> 0.4) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 39305927c0f..af70e2c1939 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -701,8 +701,7 @@ GEM method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.2) - rake + rainbow (3.0.0) raindrops (0.18.0) rake (12.3.1) rb-fsevent (0.10.2) @@ -1147,7 +1146,7 @@ DEPENDENCIES rails-controller-testing rails-deprecated_sanitizer (~> 1.0.3) rails-i18n (~> 5.1) - rainbow (~> 2.2) + rainbow (~> 3.0) raindrops (~> 0.18) rblineprof (~> 0.3.6) rbtrace (~> 0.4) diff --git a/bin/secpick b/bin/secpick index 5029fe57cfe..5e30c8e72c5 100755 --- a/bin/secpick +++ b/bin/secpick @@ -35,7 +35,9 @@ parser.parse! abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil) abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/ -branch = [BRANCH_PREFIX, options[:branch], options[:version]].join('-').freeze +branch = "#{options[:branch]}-#{options[:version]}" +branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-") +branch = branch.freeze stable_branch = "#{BRANCH_PREFIX}-#{options[:version]}".freeze command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch}" diff --git a/changelogs/unreleased/mk-bump-rainbow-gem.yml b/changelogs/unreleased/mk-bump-rainbow-gem.yml new file mode 100644 index 00000000000..31c003fb4d9 --- /dev/null +++ b/changelogs/unreleased/mk-bump-rainbow-gem.yml @@ -0,0 +1,5 @@ +--- +title: Fix bin/secpick error and security branch prefixing +merge_request: 21210 +author: +type: fixed -- cgit v1.2.1 From 8312df80033fd55cadaf0932b4383d4b7392a5a4 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 15 Aug 2018 07:45:21 +0000 Subject: Creates vue component for artifacts block --- .../jobs/components/artifacts_block.vue | 98 +++++++++++++++++ changelogs/unreleased/50101-aritfacts-block.yml | 5 + locale/gitlab.pot | 18 ++++ spec/javascripts/jobs/artifacts_block_spec.js | 120 +++++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 app/assets/javascripts/jobs/components/artifacts_block.vue create mode 100644 changelogs/unreleased/50101-aritfacts-block.yml create mode 100644 spec/javascripts/jobs/artifacts_block_spec.js diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue new file mode 100644 index 00000000000..525c5eec91a --- /dev/null +++ b/app/assets/javascripts/jobs/components/artifacts_block.vue @@ -0,0 +1,98 @@ + + diff --git a/changelogs/unreleased/50101-aritfacts-block.yml b/changelogs/unreleased/50101-aritfacts-block.yml new file mode 100644 index 00000000000..435e9d9d486 --- /dev/null +++ b/changelogs/unreleased/50101-aritfacts-block.yml @@ -0,0 +1,5 @@ +--- +title: Creates Vue component for artifacts block on job page +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4edca0c40fe..e5e818f57b9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3139,12 +3139,30 @@ msgstr "" msgid "Jobs" msgstr "" +msgid "Job|Browse" +msgstr "" + +msgid "Job|Download" +msgstr "" + +msgid "Job|Job artifacts" +msgstr "" + msgid "Job|Job has been erased" msgstr "" msgid "Job|Job has been erased by" msgstr "" +msgid "Job|Keep" +msgstr "" + +msgid "Job|The artifacts were removed" +msgstr "" + +msgid "Job|The artifacts will be removed" +msgstr "" + msgid "Jul" msgstr "" diff --git a/spec/javascripts/jobs/artifacts_block_spec.js b/spec/javascripts/jobs/artifacts_block_spec.js new file mode 100644 index 00000000000..c544c6f3e89 --- /dev/null +++ b/spec/javascripts/jobs/artifacts_block_spec.js @@ -0,0 +1,120 @@ +import Vue from 'vue'; +import { getTimeago } from '~/lib/utils/datetime_utility'; +import component from '~/jobs/components/artifacts_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Artifacts block', () => { + const Component = Vue.extend(component); + let vm; + + const expireAt = '2018-08-14T09:38:49.157Z'; + const timeago = getTimeago(); + const formatedDate = timeago.format(expireAt); + + afterEach(() => { + vm.$destroy(); + }); + + describe('with expired artifacts', () => { + it('renders expired artifact date and info', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-artifacts-removed')).not.toBeNull(); + expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).toBeNull(); + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); + + describe('with artifacts that will expire', () => { + it('renders will expire artifact date and info', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: false, + willArtifactsExpire: true, + expireAt, + }); + + expect(vm.$el.querySelector('.js-artifacts-removed')).toBeNull(); + expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).not.toBeNull(); + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); + + describe('when the user can keep the artifacts', () => { + it('renders the keep button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + keepArtifactsPath: '/keep', + }); + + expect(vm.$el.querySelector('.js-keep-artifacts')).not.toBeNull(); + }); + }); + + describe('when the user can not keep the artifacts', () => { + it('does not render the keep button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-keep-artifacts')).toBeNull(); + }); + }); + + describe('when the user can download the artifacts', () => { + it('renders the download button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + downloadArtifactsPath: '/download', + }); + + expect(vm.$el.querySelector('.js-download-artifacts')).not.toBeNull(); + }); + }); + + describe('when the user can not download the artifacts', () => { + it('does not render the keep button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-download-artifacts')).toBeNull(); + }); + }); + + describe('when the user can browse the artifacts', () => { + it('does not render the browse button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + browseArtifactsPath: '/browse', + }); + + expect(vm.$el.querySelector('.js-browse-artifacts')).not.toBeNull(); + }); + }); + + describe('when the user can not browse the artifacts', () => { + it('does not render the browse button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-browse-artifacts')).toBeNull(); + }); + }); +}); -- cgit v1.2.1 From 0d7ea0b389b2b6a8787e4960d3b8edd41852dbb2 Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Tue, 14 Aug 2018 13:18:25 +0200 Subject: Reduce number of model instances needed in test. Closes #49788. --- spec/models/internal_id_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 20600f5fa38..f2aad455d5f 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -30,7 +30,7 @@ describe InternalId do context 'with existing issues' do before do - rand(1..10).times { create(:issue, project: project) } + create_list(:issue, 2, project: project) described_class.delete_all end @@ -54,7 +54,7 @@ describe InternalId do end it 'generates a strictly monotone, gapless sequence' do - seq = (0..rand(100)).map do + seq = Array.new(10).map do described_class.generate_next(issue, scope, usage, init) end normalized = seq.map { |i| i - seq.min } -- cgit v1.2.1 From ce6a50b65175a44b897c14560d7be339fef1902d Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 14 Aug 2018 17:22:39 +0200 Subject: Add Czech as an available language New translations added to `locale/cs_CZ` will automatically be picked up. --- changelogs/unreleased/bvl-add-czech.yml | 5 +++++ lib/gitlab/i18n.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/bvl-add-czech.yml diff --git a/changelogs/unreleased/bvl-add-czech.yml b/changelogs/unreleased/bvl-add-czech.yml new file mode 100644 index 00000000000..49e0e4a74b7 --- /dev/null +++ b/changelogs/unreleased/bvl-add-czech.yml @@ -0,0 +1,5 @@ +--- +title: Add Czech as an available language. +merge_request: 21201 +author: +type: added diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 343487bc361..b8213929c6a 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -22,7 +22,8 @@ module Gitlab 'tr_TR' => 'Türkçe', 'id_ID' => 'Bahasa Indonesia', 'fil_PH' => 'Filipino', - 'pl_PL' => 'Polski' + 'pl_PL' => 'Polski', + 'cs_CZ' => 'Čeština' }.freeze def available_locales -- cgit v1.2.1 From 6e9e61dc325053328a2066bd008663489760412d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 15 Aug 2018 10:14:19 +0000 Subject: Documentation for JUnit XML Test Summary In MR widget --- doc/README.md | 1 + doc/ci/examples/README.md | 4 ++ doc/ci/img/junit_test_report.png | Bin 0 -> 9572 bytes doc/ci/junit_test_reports.md | 102 +++++++++++++++++++++++++++++++ doc/ci/yaml/README.md | 46 ++++++++++++++ doc/user/project/merge_requests/index.md | 3 +- 6 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 doc/ci/img/junit_test_report.png create mode 100644 doc/ci/junit_test_reports.md diff --git a/doc/README.md b/doc/README.md index a814c787f94..4248f62c08c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -133,6 +133,7 @@ scales to run your tests faster. - [GitLab CI/CD](ci/README.md): Explore the features and capabilities of Continuous Integration, Continuous Delivery, and Continuous Deployment with GitLab. - [Review Apps](ci/review_apps/index.md): Preview changes to your app right from a merge request. - [Pipeline Graphs](ci/pipelines.md#pipeline-graphs) +- [JUnit test reports](ci/junit_test_reports.md) ### Package diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 811f4d1f07a..8eb96ae10b2 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -43,6 +43,10 @@ There's also a collection of repositories with [example projects](https://gitlab - [Using `dpl` as deployment tool](deployment/README.md) - [The `.gitlab-ci.yml` file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) +## Test Reports + +[Collect test reports in Verify stage](../junit_test_reports.md). + ## Code Quality analysis **(Starter)** [Analyze your project's Code Quality](code_quality.md). diff --git a/doc/ci/img/junit_test_report.png b/doc/ci/img/junit_test_report.png new file mode 100644 index 00000000000..ad098eb457f Binary files /dev/null and b/doc/ci/img/junit_test_report.png differ diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md new file mode 100644 index 00000000000..5ae8ecaafa6 --- /dev/null +++ b/doc/ci/junit_test_reports.md @@ -0,0 +1,102 @@ +# JUnit test reports + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45318) in GitLab 11.2. +Requires GitLab Runner 11.2 and above. + +## Overview + +It is very common that a [CI/CD pipeline](pipelines.md) contains a +test job that will verify your code. +If the tests fail, the pipeline fails and users get notified. The person that +works on the merge request will have to check the job logs and see where the +tests failed so that they can fix them. + +You can configure your job to use JUnit test reports, and GitLab will display a +report on the merge request so that it's easier and faster to identify the +failure without having to check the entire log. + +## Use cases + +Consider the following workflow: + +1. Your `master` branch is rock solid, your project is using GitLab CI/CD and + your pipelines indicate that there isn't anything broken. +1. Someone from you team submits a merge request, a test fails and the pipeline + gets the known red icon. To investigate more, you have to go through the job + logs to figure out the cause of the failed test, which usually contain + thousands of lines. +1. You configure the JUnit test reports and immediately GitLab collects and + exposes them in the merge request. No more searching in the job logs. +1. Your development and debugging workflow becomes easier, faster and efficient. + +## How it works + +First, GitLab Runner uploads all JUnit XML files as artifacts to GitLab. Then, +when you visit a merge request, GitLab starts comparing the head and base branch's +JUnit test reports, where: + +- The base branch is the target branch (usually `master`). +- The head branch is the source branch (the latest pipeline in each merge request). + +The reports panel has a summary showing how many tests failed and how many were fixed. +If no comparison can be done because data for the base branch is not available, +the panel will just show the list of failed tests for head. + +There are three types of results: + +1. **Newly failed tests:** Test cases which passed on base branch and failed on head branch +1. **Existing failures:** Test cases which failed on base branch and failed on head branch +1. **Resolved failures:** Test cases which failed on base branch and passed on head branch + +Each entry in the panel will show the test name and its type from the list +above. Clicking on the test name will open a modal window with details of its +execution time and the error output. + +![Test Reports Widget](img/junit_test_report.png) + +## How to set it up + +NOTE: **Note:** +For a list of supported languages on JUnit tests, check the +[Wikipedia article](https://en.wikipedia.org/wiki/JUnit#Ports). + +To enable the JUnit reports in merge requests, you need to add +[`artifacts:reports:junit`](yaml/README.md#artifacts-reports-junit) +in `.gitlab-ci.yml`, and specify the path(s) of the generated test reports. + +In the following examples, the job in the `test` stage runs and GitLab +collects the JUnit test report from each job. After each job is executed, the +XML reports are stored in GitLab as artifacts and their results are shown in the +merge request widget. + +### Ruby example + +Use the following job in `.gitlab-ci.yml`: + +```yaml +## Use https://github.com/sj26/rspec_junit_formatter to generate a JUnit report with rspec +ruby: + stage: test + script: + - bundle install + - rspec spec/lib/ --format RspecJunitFormatter --out rspec.xml + artifacts: + reports: + junit: rspec.xml +``` + +### Go example + +Use the following job in `.gitlab-ci.yml`: + +```yaml +## Use https://github.com/jstemmer/go-junit-report to generate a JUnit report with go +golang: + stage: test + script: + - go get -u github.com/jstemmer/go-junit-report + - go test -v 2>&1 | go-junit-report > report.xml + artifacts: + reports: + junit: report.xml +``` diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 95d705d3a3d..ef740ab1c5e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1092,6 +1092,52 @@ job: expire_in: 1 week ``` +### `artifacts:reports` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20390) in +GitLab 11.2. Requires GitLab Runner 11.2 and above. + +The `reports` keyword is used for collecting test reports from jobs and +exposing them in GitLab's UI (merge requests, pipeline views). Read how to use +this with [JUnit reports](#artifacts-reports-junit). + +NOTE: **Note:** +The test reports are collected regardless of the job results (success or failure). +You can use [`artifacts:expire_in`](#artifacts-expire_in) to set up an expiration +date for their artifacts. + +#### `artifacts:reports:junit` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20390) in +GitLab 11.2. Requires GitLab Runner 11.2 and above. + +The `junit` report collects [JUnit XML files](https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_14.1.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html) +as artifacts. Although JUnit was originally developed in Java, there are many +[third party ports](https://en.wikipedia.org/wiki/JUnit#Ports) for other +languages like Javascript, Python, Ruby, etc. + +Below is an example of collecting a JUnit XML file from Ruby's RSpec test tool: + +```yaml +rspec: + stage: test + script: + - bundle install + - rspec --format RspecJunitFormatter --out rspec.xml + artifacts: + reports: + junit: rspec.xml +``` + +The collected JUnit reports will be uploaded to GitLab as an artifact and will +be automatically [shown in merge requests](../junit_test_reports.md). + +NOTE: **Note:** +In case the JUnit tool you use exports to multiple XML files, you can specify +multiple test report paths within a single job +(`junit: [rspec-1.xml, rspec-2.xml, rspec-3.xml]`) and they will be automatically +concatenated into a single file. + ## `dependencies` > Introduced in GitLab 8.6 and GitLab Runner v1.1.1. diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 86ecf33ed31..43ca498d006 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -43,8 +43,7 @@ A. Consider you are a software developer working in a team: 1. You checkout a new branch, and submit your changes through a merge request 1. You gather feedback from your team -1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html) **[STARTER]** -1. You build and test your changes with GitLab CI/CD +1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD 1. You request the approval from your manager 1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter) 1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD -- cgit v1.2.1 From fb9215e55b5c74fd5915fca7ce6dfbf63a871856 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 15 Aug 2018 12:09:00 +0100 Subject: Improved padding of top bar in IDE job trace panel Closes #48059 --- app/assets/stylesheets/page_bundles/ide.scss | 1 + changelogs/unreleased/ide-job-top-bar-ui-polish.yml | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 changelogs/unreleased/ide-job-top-bar-ui-polish.yml diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index af91497d0ea..eac1345742d 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -1293,6 +1293,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding; &.build-page .top-bar { top: 0; + height: auto; font-size: 12px; border-top-right-radius: $border-radius-default; } diff --git a/changelogs/unreleased/ide-job-top-bar-ui-polish.yml b/changelogs/unreleased/ide-job-top-bar-ui-polish.yml new file mode 100644 index 00000000000..d917c14e5f8 --- /dev/null +++ b/changelogs/unreleased/ide-job-top-bar-ui-polish.yml @@ -0,0 +1,5 @@ +--- +title: Improved styling of top bar in IDE job trace pane +merge_request: +author: +type: changed -- cgit v1.2.1 From 177a5e69b6d7a5425f4869d200dccf6612890a74 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 15 Aug 2018 14:17:44 +0100 Subject: Fixed deleting new files creating wrong state in IDE Closes #50255 --- app/assets/javascripts/ide/stores/mutations.js | 7 +++++- .../unreleased/ide-delete-new-files-state.yml | 5 ++++ spec/javascripts/ide/stores/mutations_spec.js | 27 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/ide-delete-new-files-state.yml diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 1eda5768709..56a8d9430c7 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -200,6 +200,7 @@ export default { }, [types.DELETE_ENTRY](state, path) { const entry = state.entries[path]; + const { tempFile = false } = entry; const parent = entry.parentPath ? state.entries[entry.parentPath] : state.trees[`${state.currentProjectId}/${state.currentBranchId}`]; @@ -209,7 +210,11 @@ export default { parent.tree = parent.tree.filter(f => f.path !== entry.path); if (entry.type === 'blob') { - state.changedFiles = state.changedFiles.concat(entry); + if (tempFile) { + state.changedFiles = state.changedFiles.filter(f => f.path !== path); + } else { + state.changedFiles = state.changedFiles.concat(entry); + } } }, [types.RENAME_ENTRY](state, { path, name, entryPath = null }) { diff --git a/changelogs/unreleased/ide-delete-new-files-state.yml b/changelogs/unreleased/ide-delete-new-files-state.yml new file mode 100644 index 00000000000..500115d19d0 --- /dev/null +++ b/changelogs/unreleased/ide-delete-new-files-state.yml @@ -0,0 +1,5 @@ +--- +title: Fixed IDE deleting new files creating wrong state +merge_request: +author: +type: fixed diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 1e836dbc3f9..6ce76aaa03b 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -213,6 +213,33 @@ describe('Multi-file store mutations', () => { expect(localState.changedFiles).toEqual([localState.entries.filePath]); }); + + it('does not add tempFile into changedFiles', () => { + localState.entries.filePath = { + deleted: false, + type: 'blob', + tempFile: true, + }; + + mutations.DELETE_ENTRY(localState, 'filePath'); + + expect(localState.changedFiles).toEqual([]); + }); + + it('removes tempFile from changedFiles when deleted', () => { + localState.entries.filePath = { + path: 'filePath', + deleted: false, + type: 'blob', + tempFile: true, + }; + + localState.changedFiles.push({ ...localState.entries.filePath }); + + mutations.DELETE_ENTRY(localState, 'filePath'); + + expect(localState.changedFiles).toEqual([]); + }); }); describe('UPDATE_FILE_AFTER_COMMIT', () => { -- cgit v1.2.1 From e113671faac0a00b25729910efa6741bf1fbcb66 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 15 Aug 2018 19:48:05 +0000 Subject: Add Acceptance testing issue template --- .gitlab/issue_templates/Acceptance_Testing.md | 100 ++++++++++++++++++++++++++ app/models/namespace.rb | 2 +- doc/development/feature_flags.md | 35 ++++++++- 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 .gitlab/issue_templates/Acceptance_Testing.md diff --git a/.gitlab/issue_templates/Acceptance_Testing.md b/.gitlab/issue_templates/Acceptance_Testing.md new file mode 100644 index 00000000000..f1fbb96ce61 --- /dev/null +++ b/.gitlab/issue_templates/Acceptance_Testing.md @@ -0,0 +1,100 @@ +## Details +- **Feature Toggle Name**: `FEATURE_NAME` +- **Required GitLab Version**: `vX.X` + +-------------------------------------------------------------------------------- + +## 1. Preparation + +- [ ] **Controllers and workers**: + 1. Please link to dashboards of the workers, and the controllers and actions that can be impacted + 2. ... + 3. ... + +## 2. Development Trial + +#### Check Dev Server Versions +- [ ] GitLab: https://dev.gitlab.org/help + +#### Enable on `dev.gitlab.org`: +- [ ] `/chatops feature set FEATURE_NAME true --dev` in [`#dev-gitlab`](https://gitlab.slack.com/messages/C6WQ87MU3) + +Then leave running while monitoring and performing some testing through web, api or SSH. + +#### Monitor + +- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net) +- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana) +- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved) + +## 2. Staging Trial + +#### Check Staging Server Versions +- [ ] GitLab: https://staging.gitlab.com/help + +#### Enable on `staging.gitlab.com` +- [ ] `/chatops run feature set FEATURE_NAME true --staging` in [`#development`](https://gitlab.slack.com/messages/C02PF508L/) + +Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH. + +#### Monitor + +- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net) +- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana) +- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved) + +## 4. Production Server Version Check + +- [ ] GitLab: https://gitlab.com/help + +## 5. Initial Impact Check + +- [ ] Enable for a subset of users, when using percentage gates: 1%. + +Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH. + +#### Monitor + +- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net) +- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana) +- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved) + +## 6. Low Impact Check + +- [ ] Enable for a bigger subset of users, when using percentage gates: 10%. + +Then leave running while monitoring for at least **30 minutes** while performing some testing through web, api or SSH. + +#### Monitor + +- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net) +- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana) +- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved) + +## 7. Mid Impact Trial + +- [ ] Enable for a big subset of users, when using percentage gates: 50%. + +Then leave running while monitoring for at least **12 hours** while performing some testing through web, api or SSH. + +#### Monitor + +- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net) +- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana) +- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved) + +## 8. Full Impact Trial + +- [ ] Enable for all users: `/chatops run feature set FEATURE_NAME true + +Then leave running while monitoring for at least **1 week**. + +#### Monitor + +- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net) +- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana) +- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved) + +#### Success? + +- [ ] Remove the feature gate from the code, and close this issue with that MR. diff --git a/app/models/namespace.rb b/app/models/namespace.rb index b974309aeb6..0deb44d7916 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -10,6 +10,7 @@ class Namespace < ActiveRecord::Base include Storage::LegacyNamespace include Gitlab::SQL::Pattern include IgnorableColumn + include FeatureGate ignore_column :deleted_at @@ -124,7 +125,6 @@ class Namespace < ActiveRecord::Base def to_param full_path end - alias_method :flipper_id, :to_param def human_name owner_name diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md index 5d1f657015c..09ea8c05be6 100644 --- a/doc/development/feature_flags.md +++ b/doc/development/feature_flags.md @@ -20,7 +20,40 @@ dynamic (querying the DB etc.). Once defined in `lib/feature.rb`, you will be able to activate a feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature) +For GitLab.com, team members have access to feature flags through chatops. Only +percentage gates are supported at this time. Setting a feature to be used 50% of +the time, you should execute `/chatops run feature set my_feature_flag 50`. + ## Feature flags for user applications GitLab does not yet support the use of feature flags in deployed user applications. -You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779). \ No newline at end of file +You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779). + +## Developing with feature flags + +In general, it's better to have a group- or user-based gate, and you should prefer +it over the use of percentage gates. This would make debugging easier, as you +filter for example logs and errors based on actors too. Futhermore, this allows +for enabling for the `gitlab-org` group first, while the rest of the users +aren't impacted. + +```ruby +# Good +Feature.enabled?(:feature_flag, project) + +# Avoid, if possible +Feature.enabled?(:feature_flag) +``` + +To use feature gates based on actors, the model needs to respond to +`flipper_id`. For example, to enable for the Foo model: + +```ruby +class Foo < ActiveRecord::Base + include FeatureGate +end +``` + +Features that are developed and are intended to be merged behind a feature flag +should not include a changelog entry. The entry should be added in the merge +request removing the feature flags. -- cgit v1.2.1 From 8184ce7fb71c84f2706fc5221cea17a940ebe0eb Mon Sep 17 00:00:00 2001 From: gfyoung Date: Wed, 15 Aug 2018 21:45:57 +0000 Subject: Enable frozen in app/mailers/**/*.rb --- app/mailers/abuse_report_mailer.rb | 2 ++ app/mailers/base_mailer.rb | 2 ++ app/mailers/devise_mailer.rb | 9 ++++++--- app/mailers/email_rejection_mailer.rb | 2 ++ app/mailers/emails/issues.rb | 2 ++ app/mailers/emails/members.rb | 2 ++ app/mailers/emails/merge_requests.rb | 2 ++ app/mailers/emails/notes.rb | 2 ++ app/mailers/emails/pages_domains.rb | 2 ++ app/mailers/emails/pipelines.rb | 8 +++++--- app/mailers/emails/profile.rb | 2 ++ app/mailers/emails/projects.rb | 2 ++ app/mailers/notify.rb | 16 ++++++++++------ app/mailers/previews/devise_mailer_preview.rb | 2 ++ app/mailers/previews/email_rejection_mailer_preview.rb | 2 ++ app/mailers/previews/notify_preview.rb | 2 ++ app/mailers/previews/repository_check_mailer_preview.rb | 2 ++ app/mailers/repository_check_mailer.rb | 2 ++ .../unreleased/frozen-string-enable-app-mailers.yml | 5 +++++ 19 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/frozen-string-enable-app-mailers.yml diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb index fe5f68ba3d5..e032f568913 100644 --- a/app/mailers/abuse_report_mailer.rb +++ b/app/mailers/abuse_report_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AbuseReportMailer < BaseMailer def notify(abuse_report_id) return unless deliverable? diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index 654468bc7fe..5fd209c4761 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseMailer < ActionMailer::Base around_action :render_with_default_locale diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index 962570a0efd..7aa75ee30e6 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeviseMailer < Devise::Mailer default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>" default reply_to: Gitlab.config.gitlab.email_reply_to @@ -9,8 +11,9 @@ class DeviseMailer < Devise::Mailer protected def subject_for(key) - subject = super - subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? - subject + subject = [super] + subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present? + + subject.join(' | ') end end diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index 76db31a4c45..45fc5a6c383 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EmailRejectionMailer < BaseMailer def rejection(reason, original_raw, can_retry = false) @reason = reason diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 392cc0bee03..c8b1ab5033a 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Issues def new_issue_email(recipient_id, issue_id, reason = nil) diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 75cf56a51f2..91dfdf58982 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Members extend ActiveSupport::Concern diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 70509e9066d..70f65d4e58d 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module MergeRequests def new_merge_request_email(recipient_id, merge_request_id, reason = nil) diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index d9a6fe2a41e..d3284e90568 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Notes def note_commit_email(recipient_id, note_id) diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb index 0027dfdc36b..ce449237ef6 100644 --- a/app/mailers/emails/pages_domains.rb +++ b/app/mailers/emails/pages_domains.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module PagesDomains def pages_domain_enabled_email(domain, recipient) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index f9f45ab987b..31e183640ad 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Pipelines def pipeline_success_email(pipeline, recipients) @@ -39,10 +41,10 @@ module Emails end def pipeline_subject(status) - commit = @pipeline.short_sha - commit << " in #{@merge_request.to_reference}" if @merge_request + commit = [@pipeline.short_sha] + commit << "in #{@merge_request.to_reference}" if @merge_request - subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit) + subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit.join(' ')) end end end diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index 4f5edeb9bda..40d7b9ccd7a 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Profile def new_user_email(user_id, token = nil) diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 761d873c01c..d7e6c2ba7b2 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Emails module Projects def project_was_moved_email(project_id, user_id, old_path_with_namespace) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0e1e39501f5..f4eeb85270e 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Notify < BaseMailer include ActionDispatch::Routing::PolymorphicRoutes include GitlabRoutingHelper @@ -92,12 +94,14 @@ class Notify < BaseMailer # >> subject('Lorem ipsum', 'Dolor sit amet') # => "Lorem ipsum | Dolor sit amet" def subject(*extra) - subject = "" - subject << "#{@project.name} | " if @project - subject << "#{@group.name} | " if @group - subject << extra.join(' | ') if extra.present? - subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? - subject + subject = [] + + subject << @project.name if @project + subject << @group.name if @group + subject.concat(extra) if extra.present? + subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present? + + subject.join(' | ') end # Return a string suitable for inclusion in the 'Message-Id' mail header. diff --git a/app/mailers/previews/devise_mailer_preview.rb b/app/mailers/previews/devise_mailer_preview.rb index d6588efc486..3b9ef0d3ac0 100644 --- a/app/mailers/previews/devise_mailer_preview.rb +++ b/app/mailers/previews/devise_mailer_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeviseMailerPreview < ActionMailer::Preview def confirmation_instructions_for_signup DeviseMailer.confirmation_instructions(unsaved_user, 'faketoken', {}) diff --git a/app/mailers/previews/email_rejection_mailer_preview.rb b/app/mailers/previews/email_rejection_mailer_preview.rb index 639e8471232..402066151ef 100644 --- a/app/mailers/previews/email_rejection_mailer_preview.rb +++ b/app/mailers/previews/email_rejection_mailer_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class EmailRejectionMailerPreview < ActionMailer::Preview def rejection EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb index 3615cde8026..df470930e9e 100644 --- a/app/mailers/previews/notify_preview.rb +++ b/app/mailers/previews/notify_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class NotifyPreview < ActionMailer::Preview def note_merge_request_email_for_individual_note note_email(:note_merge_request_email) do diff --git a/app/mailers/previews/repository_check_mailer_preview.rb b/app/mailers/previews/repository_check_mailer_preview.rb index 19d4eab1805..834d7594719 100644 --- a/app/mailers/previews/repository_check_mailer_preview.rb +++ b/app/mailers/previews/repository_check_mailer_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepositoryCheckMailerPreview < ActionMailer::Preview def notify RepositoryCheckMailer.notify(3).message diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb index 22a9f5da646..4bcf371cfc0 100644 --- a/app/mailers/repository_check_mailer.rb +++ b/app/mailers/repository_check_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepositoryCheckMailer < BaseMailer def notify(failed_count) @message = diff --git a/changelogs/unreleased/frozen-string-enable-app-mailers.yml b/changelogs/unreleased/frozen-string-enable-app-mailers.yml new file mode 100644 index 00000000000..2cd247ca76c --- /dev/null +++ b/changelogs/unreleased/frozen-string-enable-app-mailers.yml @@ -0,0 +1,5 @@ +--- +title: Enable frozen in app/mailers/**/*.rb +merge_request: 21147 +author: gfyoung +type: performance -- cgit v1.2.1 From 7f462b630a6c7476418e8c6301081e41b91c446c Mon Sep 17 00:00:00 2001 From: Jasper Maes Date: Thu, 16 Aug 2018 08:18:13 +0200 Subject: Rails5: Enable verbose query logs --- changelogs/unreleased/rails5-verbose-query-logs.yml | 5 +++++ config/initializers/active_record_verbose_query_logs.rb | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/rails5-verbose-query-logs.yml diff --git a/changelogs/unreleased/rails5-verbose-query-logs.yml b/changelogs/unreleased/rails5-verbose-query-logs.yml new file mode 100644 index 00000000000..c6d0ca9c841 --- /dev/null +++ b/changelogs/unreleased/rails5-verbose-query-logs.yml @@ -0,0 +1,5 @@ +--- +title: 'Rails5: Enable verbose query logs' +merge_request: 21231 +author: Jasper Maes +type: fixed diff --git a/config/initializers/active_record_verbose_query_logs.rb b/config/initializers/active_record_verbose_query_logs.rb index 44f86fec7e0..1c5fbc8e830 100644 --- a/config/initializers/active_record_verbose_query_logs.rb +++ b/config/initializers/active_record_verbose_query_logs.rb @@ -47,7 +47,9 @@ module ActiveRecord end end - unless Gitlab.rails5? + if Rails.version.start_with?("5.2") + raise "Remove this monkey patch: #{__FILE__}" + else prepend(VerboseQueryLogs) unless Rails.env.production? end end -- cgit v1.2.1 From d9d6b776d7671fe075a65d640bf6c2e41d93a3ec Mon Sep 17 00:00:00 2001 From: Alexander Kutelev Date: Thu, 16 Aug 2018 06:30:15 +0000 Subject: Added missing html_safe on text messages. --- app/views/projects/settings/ci_cd/_form.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index 434aed2f603..9134257b631 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -17,7 +17,7 @@ %h5.prepend-top-0 = _("Git strategy for pipelines") %p - = _("Choose between clone or fetch to get the recent application code") + = _("Choose between clone or fetch to get the recent application code").html_safe = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank' .form-check = f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' } @@ -47,7 +47,7 @@ = f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold' = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml' %p.form-text.text-muted - = _("The path to CI config file. Defaults to .gitlab-ci.yml") + = _("The path to CI config file. Defaults to .gitlab-ci.yml").html_safe = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank' %hr -- cgit v1.2.1 From 31749779da2fdc7834df5e44a5ba16789de6bb3e Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Thu, 16 Aug 2018 09:42:31 +0200 Subject: Remove feature flag for FindAllRemoteBranches Acceptance testing done through: https://gitlab.com/gitlab-org/gitaly/issues/1312 Relatively short AT period, but given its not a hard RPC, nor anything funky is going on, I felt that this was decent enough to remove the feature flag. Closes https://gitlab.com/gitlab-org/gitaly/issues/1243 --- lib/gitlab/git/repository_mirroring.rb | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb index 65eb5cc18cf..752a91fbb60 100644 --- a/lib/gitlab/git/repository_mirroring.rb +++ b/lib/gitlab/git/repository_mirroring.rb @@ -2,34 +2,7 @@ module Gitlab module Git module RepositoryMirroring def remote_branches(remote_name) - gitaly_migrate(:ref_find_all_remote_branches) do |is_enabled| - if is_enabled - gitaly_ref_client.remote_branches(remote_name) - else - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - rugged_remote_branches(remote_name) - end - end - end - end - - private - - def rugged_remote_branches(remote_name) - branches = [] - - rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref| - name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '') - - begin - target_commit = Gitlab::Git::Commit.find(self, ref.target.oid) - branches << Gitlab::Git::Branch.new(self, name, ref.target, target_commit) - rescue Rugged::ReferenceError - # Omit invalid branch - end - end - - branches + gitaly_ref_client.remote_branches(remote_name) end end end -- cgit v1.2.1 From b78a69b06c165f7a463d5e0de69030346d9d5c72 Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Tue, 31 Jul 2018 16:13:32 +0200 Subject: Trigger iid logic from GitHub importer for issues. --- .../github_import/importer/issue_importer.rb | 6 +++++- .../github_import/importer/issue_importer_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/github_import/importer/issue_importer.rb b/lib/gitlab/github_import/importer/issue_importer.rb index 31fefebf787..ead4215810f 100644 --- a/lib/gitlab/github_import/importer/issue_importer.rb +++ b/lib/gitlab/github_import/importer/issue_importer.rb @@ -55,7 +55,11 @@ module Gitlab updated_at: issue.updated_at } - GithubImport.insert_and_return_id(attributes, project.issues) + GithubImport.insert_and_return_id(attributes, project.issues).tap do |id| + # We use .insert_and_return_id which effectively disables all callbacks. + # Trigger iid logic here to make sure we track internal id values consistently. + project.issues.find(id).ensure_project_iid! + end rescue ActiveRecord::InvalidForeignKey # It's possible the project has been deleted since scheduling this # job. In this case we'll just skip creating the issue. diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb index 81fe97c1e49..3f7a12144d5 100644 --- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb @@ -78,6 +78,11 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach .to receive(:id_for) .with(issue) .and_return(milestone.id) + + allow(importer.user_finder) + .to receive(:author_id_for) + .with(issue) + .and_return([user.id, true]) end context 'when the issue author could be found' do @@ -172,6 +177,23 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach expect(importer.create_issue).to be_a_kind_of(Numeric) end + + it 'triggers internal_id functionality to track greatest iids' do + allow(importer.user_finder) + .to receive(:author_id_for) + .with(issue) + .and_return([user.id, true]) + + issue = build_stubbed(:issue, project: project) + allow(Gitlab::GithubImport) + .to receive(:insert_and_return_id) + .and_return(issue.id) + allow(project.issues).to receive(:find).with(issue.id).and_return(issue) + + expect(issue).to receive(:ensure_project_iid!) + + importer.create_issue + end end describe '#create_assignees' do -- cgit v1.2.1 From 358675d09f6ba0fdcc4a089c6d1da6df9ff6d092 Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Mon, 6 Aug 2018 15:19:15 +0200 Subject: Trigger iid logic from GitHub importer for milestones. --- app/models/internal_id.rb | 6 +++--- lib/gitlab/github_import/bulk_importing.rb | 4 +++- lib/gitlab/github_import/importer/milestones_importer.rb | 12 +++++++++++- spec/lib/gitlab/github_import/bulk_importing_spec.rb | 12 ++++++++++++ .../github_import/importer/milestones_importer_spec.rb | 14 +++++++++++++- 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb index 4eb211eff61..e7168d49db9 100644 --- a/app/models/internal_id.rb +++ b/app/models/internal_id.rb @@ -111,7 +111,7 @@ class InternalId < ActiveRecord::Base # Generates next internal id and returns it def generate - subject.transaction do + InternalId.transaction do # Create a record in internal_ids if one does not yet exist # and increment its last value # @@ -125,7 +125,7 @@ class InternalId < ActiveRecord::Base # # Note this will acquire a ROW SHARE lock on the InternalId record def track_greatest(new_value) - subject.transaction do + InternalId.transaction do (lookup || create_record).track_greatest_and_save!(new_value) end end @@ -148,7 +148,7 @@ class InternalId < ActiveRecord::Base # violation. We can safely roll-back the nested transaction and perform # a lookup instead to retrieve the record. def create_record - subject.transaction(requires_new: true) do + InternalId.transaction(requires_new: true) do InternalId.create!( **scope, usage: usage_value, diff --git a/lib/gitlab/github_import/bulk_importing.rb b/lib/gitlab/github_import/bulk_importing.rb index 147597289cf..da2f96b5c4b 100644 --- a/lib/gitlab/github_import/bulk_importing.rb +++ b/lib/gitlab/github_import/bulk_importing.rb @@ -15,10 +15,12 @@ module Gitlab end # Bulk inserts the given rows into the database. - def bulk_insert(model, rows, batch_size: 100) + def bulk_insert(model, rows, batch_size: 100, pre_hook: nil) rows.each_slice(batch_size) do |slice| + pre_hook.call(slice) if pre_hook Gitlab::Database.bulk_insert(model.table_name, slice) end + rows end end end diff --git a/lib/gitlab/github_import/importer/milestones_importer.rb b/lib/gitlab/github_import/importer/milestones_importer.rb index c53480e828a..94eb9136b9a 100644 --- a/lib/gitlab/github_import/importer/milestones_importer.rb +++ b/lib/gitlab/github_import/importer/milestones_importer.rb @@ -17,10 +17,20 @@ module Gitlab end def execute - bulk_insert(Milestone, build_milestones) + # We insert records in bulk, by-passing any standard model callbacks. + # The pre_hook here makes sure we track internal ids consistently. + # Note this has to be called before performing an insert of a batch + # because we're outside a transaction scope here. + bulk_insert(Milestone, build_milestones, pre_hook: method(:track_greatest_iid)) build_milestones_cache end + def track_greatest_iid(slice) + greatest_iid = slice.max { |e| e[:iid] }[:iid] + + InternalId.track_greatest(nil, { project: project }, :milestones, greatest_iid, ->(_) { project.milestones.maximum(:iid) }) + end + def build_milestones build_database_rows(each_milestone) end diff --git a/spec/lib/gitlab/github_import/bulk_importing_spec.rb b/spec/lib/gitlab/github_import/bulk_importing_spec.rb index 91229d9c7d4..861710f7e9b 100644 --- a/spec/lib/gitlab/github_import/bulk_importing_spec.rb +++ b/spec/lib/gitlab/github_import/bulk_importing_spec.rb @@ -58,5 +58,17 @@ describe Gitlab::GithubImport::BulkImporting do importer.bulk_insert(model, rows, batch_size: 5) end + + it 'calls pre_hook for each slice if given' do + rows = [{ title: 'Foo' }] * 10 + model = double(:model, table_name: 'kittens') + pre_hook = double('pre_hook', call: nil) + allow(Gitlab::Database).to receive(:bulk_insert) + + expect(pre_hook).to receive(:call).with(rows[0..4]) + expect(pre_hook).to receive(:call).with(rows[5..9]) + + importer.bulk_insert(model, rows, batch_size: 5, pre_hook: pre_hook) + end end end diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb index b1cac3b6e46..db0be760c7b 100644 --- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb @@ -29,13 +29,25 @@ describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab_redis expect(importer) .to receive(:bulk_insert) - .with(Milestone, [milestone_hash]) + .with(Milestone, [milestone_hash], any_args) expect(importer) .to receive(:build_milestones_cache) importer.execute end + + it 'tracks internal ids' do + milestone_hash = { iid: 1, title: '1.0', project_id: project.id } + allow(importer) + .to receive(:build_milestones) + .and_return([milestone_hash]) + + expect(InternalId).to receive(:track_greatest) + .with(nil, { project: project }, :milestones, 1, any_args) + + importer.execute + end end describe '#build_milestones' do -- cgit v1.2.1 From fb98496f49bbb324b808523ea97f0844682fe1ac Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Mon, 6 Aug 2018 15:36:31 +0200 Subject: Trigger iid logic from GitHub importer for merge requests. --- lib/gitlab/github_import/importer/pull_request_importer.rb | 8 +++++++- .../github_import/importer/pull_request_importer_spec.rb | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/github_import/importer/pull_request_importer.rb b/lib/gitlab/github_import/importer/pull_request_importer.rb index 6b3688c4381..e4b49d2143a 100644 --- a/lib/gitlab/github_import/importer/pull_request_importer.rb +++ b/lib/gitlab/github_import/importer/pull_request_importer.rb @@ -76,7 +76,13 @@ module Gitlab merge_request_id = GithubImport .insert_and_return_id(attributes, project.merge_requests) - [project.merge_requests.find(merge_request_id), false] + merge_request = project.merge_requests.find(merge_request_id) + + # We use .insert_and_return_id which effectively disables all callbacks. + # Trigger iid logic here to make sure we track internal id values consistently. + merge_request.ensure_target_project_iid! + + [merge_request, false] end rescue ActiveRecord::InvalidForeignKey # It's possible the project has been deleted since scheduling this diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index 3422a1e82fc..44c920043b4 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -111,6 +111,16 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi expect(mr).to be_instance_of(MergeRequest) expect(exists).to eq(false) end + + it 'triggers internal_id functionality to track greatest iids' do + mr = build_stubbed(:merge_request, source_project: project, target_project: project) + allow(Gitlab::GithubImport).to receive(:insert_and_return_id).and_return(mr.id) + allow(project.merge_requests).to receive(:find).with(mr.id).and_return(mr) + + expect(mr).to receive(:ensure_target_project_iid!) + + importer.create_merge_request + end end context 'when the author could not be found' do -- cgit v1.2.1 From 295e5821424fe1681eeaccd5fef5e21a4d23d006 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Thu, 16 Aug 2018 10:24:26 +0200 Subject: Remove feature flags for UserUpdateBranch This has been tested on .com. So far no errors have been seen. Closes https://gitlab.com/gitlab-org/gitaly/issues/1310 Closes https://gitlab.com/gitlab-org/gitaly/issues/1246 --- lib/gitlab/git/repository.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index e9c901f8592..9521a2d63a0 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -543,14 +543,8 @@ module Gitlab end def update_branch(branch_name, user:, newrev:, oldrev:) - gitaly_migrate(:operation_user_update_branch) do |is_enabled| - if is_enabled - gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev) - else - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - OperationService.new(user, self).update_branch(branch_name, newrev, oldrev) - end - end + wrapped_gitaly_errors do + gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev) end end -- cgit v1.2.1 From b8297c122f439e242b8a9790a9b66622709306eb Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 16 Aug 2018 11:04:33 +0200 Subject: Use gitlab-workhorse 6.0.0 --- GITLAB_WORKHORSE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 831446cbd27..09b254e90c6 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -5.1.0 +6.0.0 -- cgit v1.2.1 From 819f6d9bceda9a618c2983484ccd3ef59f97fdde Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Thu, 16 Aug 2018 09:30:04 +0000 Subject: Documentation process at GitLab --- .gitlab/issue_templates/Documentation.md | 54 ++++++ .../Change documentation location.md | 32 ++++ .gitlab/merge_request_templates/Documentation.md | 35 ++-- PROCESS.md | 33 +++- doc/development/documentation/index.md | 66 +++----- doc/development/documentation/structure.md | 149 +++++++++++++++++ doc/development/documentation/styleguide.md | 26 ++- doc/development/documentation/workflow.md | 186 +++++++++++++++++++++ 8 files changed, 509 insertions(+), 72 deletions(-) create mode 100644 .gitlab/issue_templates/Documentation.md create mode 100644 .gitlab/merge_request_templates/Change documentation location.md create mode 100644 doc/development/documentation/structure.md create mode 100644 doc/development/documentation/workflow.md diff --git a/.gitlab/issue_templates/Documentation.md b/.gitlab/issue_templates/Documentation.md new file mode 100644 index 00000000000..b33ed5bcaa8 --- /dev/null +++ b/.gitlab/issue_templates/Documentation.md @@ -0,0 +1,54 @@ + + + + + + + + +- [ ] Documents Feature A +- [ ] Follow-up from: #XXX, !YYY + +## New doc or update? + + + +- [ ] New documentation +- [ ] Update existing documentation + +## Checklists + +### Product Manager + + + +- [ ] Add the correct labels +- [ ] Add the correct milestone +- [ ] Indicate the correct document/directory for this feature +- [ ] Fill the doc blurb below + +#### Documentation blurb + + + +- Doc **title** + + + +- Feature **overview/description** + + + +- Feature **use cases** + + + +### Developer + + + +- [ ] Copy the doc blurb above and paste it into the doc +- [ ] Write the tutorial - explain how to use the feature +- [ ] Submit the MR using the appropriate MR description template + +/label ~Documentation diff --git a/.gitlab/merge_request_templates/Change documentation location.md b/.gitlab/merge_request_templates/Change documentation location.md new file mode 100644 index 00000000000..b4a6d2bd3b4 --- /dev/null +++ b/.gitlab/merge_request_templates/Change documentation location.md @@ -0,0 +1,32 @@ + + + + +## What does this MR do? + + + +## Related issues + + + +Closes + +## Moving docs to a new location? + +Read the guidelines: +https://docs.gitlab.com/ce/development/documentation/index.html#changing-document-location + +- [ ] Make sure the old link is not removed and has its contents replaced with + a link to the new location. +- [ ] Make sure internal links pointing to the document in question are not broken. +- [ ] Search and replace any links referring to old docs in GitLab Rails app, + specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories. +- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments) + to the new document if there are any Disqus comments on the old document thread. +- [ ] Update the link in `features.yml` (if applicable) +- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE + with the changes as well (https://docs.gitlab.com/ce/development/writing_documentation.html#cherry-picking-from-ce-to-ee). +- [ ] Ping one of the technical writers for review. + +/label ~Documentation diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index 531035b3766..ca38c881c66 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -1,4 +1,8 @@ - + + + + + ## What does this MR do? @@ -10,20 +14,19 @@ Closes -## Moving docs to a new location? - -Read the guidelines: -https://docs.gitlab.com/ee/development/documentation/#changing-document-location - -- [ ] Make sure the old link is not removed and has its contents replaced with - a link to the new location. -- [ ] Make sure internal links pointing to the document in question are not broken. -- [ ] Search and replace any links referring to the old docs in the GitLab Rails app, - specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories. -- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/documentation/index.html#redirections-for-pages-with-disqus-comments) - to the new document if there are any Disqus comments on the old document thread. -- [ ] If working on CE and the `ee-compat-check` jobs fails, [submit an MR to EE - with the changes](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee) as well. -- [ ] Ping one of the technical writers for review. +## Author's checklist + +- [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process) +- [ ] Crosslink the document from the higher-level index +- [ ] Crosslink the document from other subject-related docs +- [ ] Correctly apply the product [badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) and [tiers](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers) +- [ ] [Port the MR to EE (or backport from CE)](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee): _always recommended, required when the `ee-compat-check` job fails_ + +## Review checklist + +- [ ] Your team's review (required) +- [ ] PM's review (recommended, but not a blocker) +- [ ] Technical writer's review (required) +- [ ] Merge the EE-MR first, CE-MR afterwards /label ~Documentation diff --git a/PROCESS.md b/PROCESS.md index 5f50d472bd7..583f36b820f 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -116,6 +116,11 @@ target. However, if one does and falls into either of the above categories, it's the reviewer's responsibility to manage the above communication and assignment on behalf of the community member. +Every new feature or change should be shipped with its corresponding documentation +in accordance with the +[documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html) +and [structure](https://docs.gitlab.com/ee/development/documentation/structure.html). + #### What happens if these deadlines are missed? If a small or large feature is _not_ with a maintainer or reviewer by the @@ -141,14 +146,9 @@ and to prevent any last minute surprises. ### On the 7th -Merge requests should still be complete, following the -[definition of done][done]. The single exception is documentation, and this can -only be left until after the freeze if: +Merge requests should still be complete, following the [definition of done][done]. -* There is a follow-up issue to add documentation. -* It is assigned to the person writing documentation for this feature, and they - are aware of it. -* It is in the correct milestone, with the ~Deliverable label. +#### Feature merge requests If a merge request is not ready, but the developers and Product Manager responsible for the feature think it is essential that it is in the release, @@ -164,6 +164,23 @@ information, see [Automatic CE->EE merge][automatic_ce_ee_merge] and [Guidelines for implementing Enterprise Edition features][ee_features]. +#### Documentation merge requests + +Documentation is part of the product and must be shipped with the feature. + +The single exception for the feature freeze is documentation, and it can +be left to be **merged up to the 14th** if: + +* There is a follow-up issue to add documentation. +* It is assigned to the developer writing documentation for this feature, and they + are aware of it. +* It is in the correct milestone, with the labels ~Documentation, ~Deliverable, +~missed-deliverable, and "pick into X.Y" applied. +* It must be reviewed and approved by a technical writer. + +For more information read the process for +[documentation shipped late](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late). + ### After the 7th Once the stable branch is frozen, the only MRs that can be cherry-picked into @@ -172,7 +189,7 @@ the stable branch are: * Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing bugs](#managing-bugs) section. * Fixes for security issues * Fixes or improvements to automated QA scenarios -* Documentation updates for changes in the same release +* [Documentation updates](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late) for changes in the same release * New or updated translations (as long as they do not touch application code) During the feature freeze all merge requests that are meant to go into the diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index f5cdd310f6f..f46c171d9f1 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -25,52 +25,23 @@ them to review it for you. We use the [monthly release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/#monthly-releases) as a changelog checklist to ensure everything is documented. -Whenever you submit a merge request for the documentation, use the documentation MR description template. +Whenever you submit a merge request for the documentation, use the +"Documentation" MR description template. If you're changing documentation +location, use the MR description template called "Change documentation +location" instead. -Please check the [documentation workflow](https://about.gitlab.com/handbook/product/technical-writing/workflow/) before getting started. +## Documentation workflow -## Documentation structure - -- Overview and use cases: what it is, why it is necessary, why one would use it -- Requirements: what do we need to get started -- Tutorial: how to set it up, how to use it - -Always link a new document from its topic-related index, otherwise, it will -not be included it in the documentation site search. - -_Note: to be extended._ - -### Feature overview and use cases - -Every major feature (regardless if present in GitLab Community or Enterprise editions) -should present, at the beginning of the document, two main sections: **overview** and -**use cases**. Every GitLab EE-only feature should also contain these sections. +Please read through the [documentation workflow](workflow.md) before getting started. -**Overview**: as the name suggests, the goal here is to provide an overview of the feature. -Describe what is it, what it does, why it is important/cool/nice-to-have, -what problem it solves, and what you can do with this feature that you couldn't -do before. - -**Use cases**: provide at least two, ideally three, use cases for every major feature. -You should answer this question: what can you do with this feature/change? Use cases -are examples of how this feature or change can be used in real life. - -Examples: -- CE and EE: [Issues](../user/project/issues/index.md#use-cases) -- CE and EE: [Merge Requests](../user/project/merge_requests/index.md#overview) -- EE-only: [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html#overview) -- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.md#overview) - -Note that if you don't have anything to add between the doc title (`

`) and -the header `## Overview`, you can omit the header, but keep the content of the -overview there. +## Documentation structure -> **Overview** and **use cases** are required to **every** Enterprise Edition feature, -and for every **major** feature present in Community Edition. +Follow through the [documentation structure guide](structure.md) for learning +how to structure GitLab docs. ## Markdown and styles -Currently GitLab docs use Redcarpet as [markdown](../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future. +Currently GitLab docs use Redcarpet as [markdown](../../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future. All the docs follow the [documentation style guidelines](styleguide.md). @@ -84,9 +55,18 @@ In order to have a [solid site structure](https://searchengineland.com/seo-benef all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent. The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have -been deprecated and the majority their docs have been moved to their correct location +been **deprecated** and the majority their docs have been moved to their correct location in small iterations. Please don't create new docs in these folders. +### Documentation files + +- When you create a new directory, always start with an `index.md` file. +Do not use another file name and **do not** create `README.md` files +- **Do not** use special chars and spaces, or capital letters in file names, +directory names, branch names, and anything that generates a path. +- Max screenshot size: 100KB +- We do not support videos (yet) + ### Location and naming documents The documentation hierarchy can be vastly improved by providing a better layout @@ -116,7 +96,7 @@ The table below shows what kind of documentation goes where. --- -**General rules:** +**General rules & best practices:** 1. The correct naming and location of a new document, is a combination of the relative URL of the document in question and the GitLab Map design @@ -203,7 +183,7 @@ Things to note: documentation, sometimes it might be useful to search a path deeper. - The `*.md` extension is not used when a document is linked to GitLab's built-in help page, that's why we omit it in `git grep`. -- Use the checklist on the documentation MR description template. +- Use the checklist on the "Change documentation location" MR description template. #### Alternative redirection method @@ -514,7 +494,7 @@ Suppose there's a process to go from point A to point B in 5 steps: `(A) 1 > 2 > A **guide** can be understood as a description of certain processes to achieve a particular objective. A guide brings you from A to B describing the characteristics of that process, but not necessarily going over each step. It can mention, for example, steps 2 and 3, but does not necessarily explain how to accomplish them. -- Live example: "[Static sites and GitLab Pages domains (Part 1)](../user/project/pages/getting_started_part_one.md) to [Creating and Tweaking GitLab CI/CD for GitLab Pages (Part 4)](../../user/project/pages/getting_started_part_four.md)" +- Live example: "[Static sites and GitLab Pages domains (Part 1)](../../user/project/pages/getting_started_part_one.md) to [Creating and Tweaking GitLab CI/CD for GitLab Pages (Part 4)](../../user/project/pages/getting_started_part_four.md)" A **tutorial** requires a clear **step-by-step** guidance to achieve a singular objective. It brings you from A to B, describing precisely all the necessary steps involved in that process, showing each of the 5 steps to go from A to B. It does not only describes steps 2 and 3, but also shows you how to accomplish them. diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md new file mode 100644 index 00000000000..1002836096a --- /dev/null +++ b/doc/development/documentation/structure.md @@ -0,0 +1,149 @@ +--- +description: Learn the how to correctly structure GitLab documentation. +--- + +# Documentation structure + +For consistency throughout the documentation, it's important to maintain the same +structure among the docs. + +Before getting started, read through the following docs: + +- [Contributing to GitLab documentation](index.md#contributing-to-docs) +- [Merge requests for GitLab documentation](index.md#merge-requests-for-gitlab-documentation) +- [Branch naming for docs-only changes](index.md#branch-naming) +- [Documentation directory structure](index.md#documentation-directory-structure) +- [Documentation style guidelines](styleguide.md) +- [Documentation workflow](workflow.md) + +## Documentation blurb + +Every document should include the following content in the following sequence: + +- **Feature name**: defines an intuitive name for the feature that clearly +states what it is and is consistent with any relevant UI text. +- **Feature overview** and description: describe what it is, what it does, and in what context it should be used. +- **Use cases**: describes real use case scenarios for that feature. +- **Requirements**: describes what software and/or configuration is required to be able to +use the feature and, if applicable, prerequisite knowledge for being able to follow/implement the tutorial. +For example, familiarity with GitLab CI/CD, an account on a third-party service, dependencies installed, etc. +Link each one to its most relevant resource; i.e., where the reader can go to begin to fullfil that requirement. +(Another doc page, a third party application's site, etc.) +- **Instructions**: clearly describes the steps to use the feature, leaving no gaps. +- **Troubleshooting** guide (recommended but not required): if you know beforehand what issues +one might have when setting it up, or when something is changed, or on upgrading, it's +important to describe those too. Think of things that may go wrong and include them in the +docs. This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. Answering them beforehand only makes your +document better and more approachable. + +For additional details, see the subsections below, as well as the [Documentation template for new docs](#Documentation-template-for-new-docs). + +### Feature overview and use cases + +Every major feature (regardless if present in GitLab Community or Enterprise editions) +should present, at the beginning of the document, two main sections: **overview** and +**use cases**. Every GitLab EE-only feature should also contain these sections. + +**Overview**: as the name suggests, the goal here is to provide an overview of the feature. +Describe what is it, what it does, why it is important/cool/nice-to-have, +what problem it solves, and what you can do with this feature that you couldn't +do before. + +**Use cases**: provide at least two, ideally three, use cases for every major feature. +You should answer this question: what can you do with this feature/change? Use cases +are examples of how this feature or change can be used in real life. + +Examples: +- CE and EE: [Issues](../user/project/issues/index.md#use-cases) +- CE and EE: [Merge Requests](../user/project/merge_requests/index.md#overview) +- EE-only: [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html#overview) +- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.md#overview) + +Note that if you don't have anything to add between the doc title (`

`) and +the header `## Overview`, you can omit the header, but keep the content of the +overview there. + +> **Overview** and **use cases** are required to **every** Enterprise Edition feature, +and for every **major** feature present in Community Edition. + +### Discoverability + +Your new document will be discoverable by the user only if: + +- Crosslinked from the higher-level index (e.g., Issue Boards docs +should be linked from Issues; Prometheus docs should be linked from +Monitoring; CI/CD tutorials should be linked from CI/CD examples). + - When referencing other GitLab products and features, link to their +respective docs; when referencing third-party products or technologies, +link out to their external sites, documentation, and resources. +- The headings are clear. E.g., "App testing" is a bad heading, "Testing +an application with GitLab CI/CD" is much better. Think of something +someone will search for and use these keywords in the headings. + +## Documentation template for new docs + +To start a new document, respect the file tree and file name guidelines, +as well as the style guidelines. Use the following template: + +```md +--- +description: "short document description." # Up to ~200 chars long. They will be displayed in Google Search Snippets. +--- + +# Feature Name **[TIER]** (1) + +> [Introduced](link_to_issue_or_mr) in GitLab Tier X.Y (2). + +A short description for the feature (can be the same used in the frontmatter's +`description`). + +## Overview + +To write the feature overview, you should consider answering the following questions: + +- What is it? +- Who is it for? +- What is the context in which it is used and are there any prerequisites/requirements? +- What can the user do with it? (Be sure to consider multiple audiences, like GitLab admin and developer-user.) +- What are the benefits to using it over any alternatives? + +## Use cases + +Describe one to three use cases for that feature. Give real-life examples. + +## Requirements + +State any requirements, if any, for using the feature and/or following along with the tutorial. + +The only assumption that is redundant and doesn't need to be mentioned is having an account +on GitLab. + +## Instructions + +("Instructions" is not necessarily the name of the heading) + +- Write a step-by-step guide, with no gaps between the steps. +- Start with an h2 (`##`), break complex steps into small steps using +subheadings h3 > h4 > h5 > h6. _Never skip the hierarchy level, such +as h2 > h4_, as it will break the TOC and may affect the breadcrumbs. +- Use short and descriptive headings (up to ~50 chars). You can use one +single heading `## How it works` for the instructions when the feature +is simple and the document is short. +- Be clear, concise, and stick to the goal of the doc: explain how to +use that feature. +- Use inclusive language and avoid jargons, as well as uncommon and +fancy words. The docs should be clear and very easy to understand. +- Write in the 3rd person (use "we", "you", "us", "one", instead of "I" or "me"). +- Always provide internal and external reference links. +- Always link the doc from its higher-level index. + + +``` + +Notes: + +- (1): Apply the [tier badges](styleguide.md#product-badges) accordingly +- (2): Apply the correct format for the [GitLab version introducing the feature](styleguide.md#gitlab-versions-and-tiers) diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index ad49c77aac8..6c60a517b6d 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -10,6 +10,22 @@ GitLab documentation. Check the Check the GitLab handbook for the [writing styles guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines). +## Files + +- [Directory structure](index.md#location-and-naming-documents): place the docs +in the correct location +- [Documentation files](index.md#documentation-files): name the files accordingly +- [Markdown](../../user/markdown.md): use the GitLab Flavored Markdown in the +documentation + +NOTE: **Note:** +**Do not** use capital letters, spaces, or special chars in file names, +branch names, directory names, headings, or in anything that generates a path. + +NOTE: **Note:** +**Do not** create new `README.md` files, name them `index.md` instead. There's +a test that will fail if it spots a new `README.md` file. + ## Text - Split up long lines (wrap text), this makes it much easier to review and edit. Only @@ -61,7 +77,8 @@ For punctuation rules, please refer to the [GitLab UX guide](https://design.gitl - Add **only one H1** in each document, by adding `#` at the beginning of it (when using markdown). The `h1` will be the document ``. -- For subheadings, use `##`, `###` and so on +- Start with an h2 (`##`), and respect the order h2 > h3 > h4 > h5 > h6. + Never skip the hierarchy level, such as h2 > h4 - Avoid putting numbers in headings. Numbers shift, hence documentation anchor links shift too, which eventually leads to dead links. If you think it is compelling to add numbers in headings, make sure to at least discuss it with @@ -115,10 +132,7 @@ needs to expand the tab to find the settings you're referring to the `.md` document that you're working on is located. Always prepend their names with the name of the document that they will be included in. For example, if there is a document called `twitter.md`, then a valid image name - could be `twitter_login_screen.png`. [**Exception**: images for - [articles](index.md#technical-articles) should be - put in a directory called `img` underneath `/articles/article_title/img/`, therefore, - there's no need to prepend the document name to their filenames.] + could be `twitter_login_screen.png`. - Images should have a specific, non-generic name that will differentiate them. - Keep all file names in lower case. - Consider using PNG images instead of JPEG. @@ -126,6 +140,8 @@ needs to expand the tab to find the settings you're referring to - Compress gifs with <https://ezgif.com/optimize> or similar tool. - Images should be used (only when necessary) to _illustrate_ the description of a process, not to _replace_ it. +- Max image size: 100KB (gifs included). +- The GitLab docs do not support videos yet. Inside the document: diff --git a/doc/development/documentation/workflow.md b/doc/development/documentation/workflow.md new file mode 100644 index 00000000000..339ec80f889 --- /dev/null +++ b/doc/development/documentation/workflow.md @@ -0,0 +1,186 @@ +--- +description: Learn the process of shipping documentation for GitLab. +--- + +# Documentation process at GitLab + +At GitLab, developers contribute new or updated documentation along with their code, but product managers and technical writers also have essential roles in the process. + +- Product Managers (PMs): in the issue for all new and updated features, +PMs include specific documentation requirements that the developer who is +writing or updating the docs must meet, along with feature descriptions +and use cases. They call out any specific areas where collaborating with +a technical writer is recommended, and usually act as the first reviewer +of the docs. +- Developers: author documentation and merge it on time (up to a week after +the feature freeze). +- Technical Writers: review each issue to ensure PM's requirements are complete, +help developers with any questions throughout the process, and act as the final +reviewer of all new and updated docs content before it's merged. + +## Requirements + +Documentation must be delivered whenever: + +- A new feature is shipped +- There are changes to the UI +- A process, workflow, or previously documented feature is changed + +Documentation is not required when a feature is changed on the backend +only and does not directly affect the way that any regular user or +administrator would interact with GitLab. + +NOTE: **Note:** +When refactoring documentation in needed, it should be submitted it in its own MR. +**Do not** join new features' MRs with refactoring existing docs, as they might have +different priorities. + +NOTE: **Note:** +[Smaller MRs are better](https://gitlab.com/gitlab-com/blog-posts/issues/185#note_4401010)! Do not mix subjects, and ship the smallest MR possible. + +### Documentation review process + +The docs shipped by the developer should be reviewed by the PM (for accuracy) and a Technical Writer (for clarity and structure). + +#### Documentation updates that require Technical Writer review + +Every documentation change that meets the criteria below must be reviewed by a Technical Writer +to ensure clarity and discoverability, and avoid redundancy, bad file locations, typos, broken links, etc. +Within the GitLab issue or MR, ping the relevant technical writer for the subject area. If you're not sure who that is, +ping any of them or all of them (`@gl\-docsteam`). + +A Technical Writer must review documentation updates that involve: + +- Docs introducing new features +- Changing documentation location +- Refactoring existing documentation +- Creating new documentation files + +If you need any help to choose the correct place for a doc, discuss a documentation +idea or outline, or request any other help, ping a Technical Writer on your issue, MR, +or on Slack in `#docs`. + +#### Skip the PM's review + +When there's a non-significant change to the docs, you can skip the review +of the PM. Add the same labels as you would for a regular doc change and +assign the correct milestone. In these cases, assign a Technical Writer +for approval/merge, or mention `@gl\-docsteam` in case you don't know +which Tech Writer to assign for. + +#### Skip the entire review + +When the MR only contains corrections to the content (typos, grammar, +broken links, etc), it can be merged without the PM's and Tech Writer's review. + +## Documentation structure + +Read through the [documentation structure](structure.md) docs for an overview. + +## Documentation workflow + +To follow a consistent workflow every month, documentation changes +involve the Product Managers, the developer who shipped the feature, +and the Technical Writing team. Each role is described below. + +### 1. Product Manager's role in the documentation process + +The Product Manager (PM) should add to the feature issue: + +- Feature name, overview/description, and use cases, for the [documentation blurb](structure.md#documentation-blurb) +- The documentation requirements for the developer working on the docs + - What new page, new subsection of an existing page, or other update to an existing page/subsection is needed. + - Just one page/section/update or multiple (perhaps there's an end user and admin change needing docs, or we need to update a previously recommended workflow, or we want to link the new feature from various places; consider and mention all ways documentation should be affected + - Suggested title of any page or subsection, if applicable +- Label the issue with `Documentation`, `Deliverable`, `docs:P1`, and assign + the correct milestone + +### 2. Developer's role in the documentation process + +As a developer, or as a community contributor, you should ship the documentation +with the feature, as in GitLab the documentation is part of the product. + +The docs can either be shipped along with the MR introducing the code, or, +alternatively, created from a follow-up issue and MR. + +The docs should be shipped **by the feature freeze date**. Justified +exceptions are accepted, as long as the [following process](#documentation-shipped-late) +and the missed-deliverable due date (the 14th of each month) are both respected. + +#### Documentation shipped in the feature MR + +The developer should add to the feature MR the documentation containing: + +- The [documentation blurb](structure.md#documentation-blurb): copy the +feature name, overview/description, and use cases from the feature issue +- Instructions: write how to use the feature, step by step, with no gaps. +- [Crosslink for discoverability](structure.md#discoverability): link with +internal docs and external resources (if applicable) +- Index: link the new doc or the new heading from the higher-level index +for [discoverability](#discoverability) +- [Screenshots](styleguide.md#images): when necessary, add screenshots for: + - Illustrating a step of the process + - Indicating the location of a navigation menu +- Label the MR with `Documentation`, `Deliverable`, `docs-P1`, and assign +the correct milestone +- Assign the PM for review +- When done, mention the `@gl\-docsteam` in the MR asking for review +- **Due date**: feature freeze date and time + +#### Documentation shipped in a follow-up MR + +If the docs aren't being shipped within the feature MR: + +- Create a new issue mentioning "docs" or "documentation" in the title (use the Documentation issue description template) +- Label the issue with: `Documentation`, `Deliverable`, `docs-P1`, `<product-label>` +(product label == CI/CD, Pages, Prometheus, etc) +- Add the correct milestone +- Create a new MR for shipping the docs changes and follow the same +process [described above](#documentation-shipped-in-the-feature-mr) +- Use the MR description template called "Documentation" +- Add the same labels and milestone as you did for the issue +- Assign the PM for review +- When done, mention the `@gl\-docsteam` in the MR asking for review +- **Due date**: feature freeze date and time + +#### Documentation shipped late + +Shipping late means that you are affecting the whole feature workflow +as well as other teams' priorities (PMs, tech writers, release managers, +release post reviewers), so every effort should be made to avoid this. + +If you did not ship the docs within the feature freeze, proceed as +[described above](#documentation-shipped-in-a-follow-up-mr) and, +besides the regular labels, include the labels `Pick into X.Y` and +`missed-deliverable` in the issue and the MR, and assign them the correct +milestone. + +The **due date** for **merging** `missed-deliverable` MRs is on the +**14th** of each month. + +### 3. Technical Writer's role in the documentation process + +- **Planning** + - Once an issue contains a Documentation label and the current milestone, a +technical writer reviews the Product Manager's documentation requirements + - Once the documentation requirements are approved, the technical writer can +work with the developer to discuss any documentation questions and plans/outlines, as needed. + +- **Review** - A technical writer must review the documentation for: + - Clarity + - Relevance (make sure the content is appropriate given the impact of the feature) + - Location (make sure the doc is in the correct dir and has the correct name) + - Syntax, typos, and broken links + - Improvements to the content + - Accordance to the [docs style guide](styleguide.md) + +<!-- TBA: issue and MR description templates as part of the process --> + +<!-- +## New features vs feature updates + +- TBA: + - Describe the difference between new features and feature updates + - Creating a new doc vs updating an existing doc +--> + -- cgit v1.2.1 From 182f1eeb27d63541141f339b54501cc821b8e0e9 Mon Sep 17 00:00:00 2001 From: Luke Bennett <lukeeeebennettplus@gmail.com> Date: Thu, 16 Aug 2018 10:35:19 +0100 Subject: add changelog --- ...ance-statistics-convdev-index-intro-banner-is-not-dismissable.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/50312-instance-statistics-convdev-index-intro-banner-is-not-dismissable.yml diff --git a/changelogs/unreleased/50312-instance-statistics-convdev-index-intro-banner-is-not-dismissable.yml b/changelogs/unreleased/50312-instance-statistics-convdev-index-intro-banner-is-not-dismissable.yml new file mode 100644 index 00000000000..50a3b9c9aff --- /dev/null +++ b/changelogs/unreleased/50312-instance-statistics-convdev-index-intro-banner-is-not-dismissable.yml @@ -0,0 +1,5 @@ +--- +title: Fix issue stopping Instance Statistics javascript to be executed +merge_request: 21211 +author: +type: fixed -- cgit v1.2.1 From 5fbb6ddf23da52cf68ed4f87137debfb6f30f6e8 Mon Sep 17 00:00:00 2001 From: Valery Sizov <valery@gitlab.com> Date: Mon, 13 Aug 2018 14:54:31 +0300 Subject: Fix: Project deletion may not log audit events during group deletion --- app/services/groups/destroy_service.rb | 7 ++++--- ...eletion-may-not-log-audit-events-during-group-deletion.yml | 5 +++++ spec/services/groups/destroy_service_spec.rb | 11 +++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-group-deletion.yml diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index c4554ce45fb..12aeba4af71 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -2,6 +2,8 @@ module Groups class DestroyService < Groups::BaseService + DestroyError = Class.new(StandardError) + def async_execute job_id = GroupDestroyWorker.perform_async(group.id, current_user.id) Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") @@ -12,9 +14,8 @@ module Groups group.projects.each do |project| # Execute the destruction of the models immediately to ensure atomic cleanup. - # Skip repository removal because we remove directory with namespace - # that contain all these repositories - ::Projects::DestroyService.new(project, current_user, skip_repo: project.legacy_storage?).execute + success = ::Projects::DestroyService.new(project, current_user).execute + raise DestroyError, "Project #{project.id} can't be deleted" unless success end group.children.each do |group| diff --git a/changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-group-deletion.yml b/changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-group-deletion.yml new file mode 100644 index 00000000000..bb7633abdb1 --- /dev/null +++ b/changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-group-deletion.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix: Project deletion may not log audit events during group deletion' +merge_request: 21162 +author: +type: fixed diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index b54491cf5f9..d80d0f5a8a8 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -135,6 +135,17 @@ describe Groups::DestroyService do it_behaves_like 'group destruction', false end + context 'repository removal status is taken into account' do + it 'raises exception' do + expect_next_instance_of(::Projects::DestroyService) do |destroy_service| + expect(destroy_service).to receive(:execute).and_return(false) + end + + expect { destroy_group(group, user, false) } + .to raise_error(Groups::DestroyService::DestroyError, "Project #{project.id} can't be deleted" ) + end + end + describe 'repository removal' do before do destroy_group(group, user, false) -- cgit v1.2.1 From 8369ea8972f1c244bf1fe37f1128f824be25f69b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Thu, 16 Aug 2018 10:45:14 +0000 Subject: Creates empty state component for job log view --- .../javascripts/jobs/components/empty_state.vue | 76 ++++++++++++++++++ .../unreleased/50101-empty-state-component.yml | 5 ++ spec/javascripts/jobs/empty_state_spec.js | 90 ++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 app/assets/javascripts/jobs/components/empty_state.vue create mode 100644 changelogs/unreleased/50101-empty-state-component.yml create mode 100644 spec/javascripts/jobs/empty_state_spec.js diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue new file mode 100644 index 00000000000..4faf08387fb --- /dev/null +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -0,0 +1,76 @@ +<script> + export default { + props: { + illustrationPath: { + type: String, + required: true, + }, + illustrationSizeClass: { + type: String, + required: true, + }, + title: { + type: String, + required: true, + }, + content: { + type: String, + required: false, + default: null, + }, + action: { + type: Object, + required: false, + default: null, + validator(value) { + return ( + value === null || + (Object.prototype.hasOwnProperty.call(value, 'link') && + Object.prototype.hasOwnProperty.call(value, 'method') && + Object.prototype.hasOwnProperty.call(value, 'title')) + ); + }, + }, + }, + }; +</script> +<template> + <div class="row empty-state"> + <div class="col-12"> + <div + :class="illustrationSizeClass" + class="svg-content" + > + <img :src="illustrationPath" /> + </div> + </div> + + <div class="col-12"> + <div class="text-content"> + <h4 class="js-job-empty-state-title text-center"> + {{ title }} + </h4> + + <p + v-if="content" + class="js-job-empty-state-content" + > + {{ content }} + </p> + + <div + v-if="action" + class="text-center" + > + <a + :href="action.link" + :data-method="action.method" + class="js-job-empty-state-action btn btn-primary" + > + {{ action.title }} + </a> + </div> + </div> + </div> + </div> +</template> diff --git a/changelogs/unreleased/50101-empty-state-component.yml b/changelogs/unreleased/50101-empty-state-component.yml new file mode 100644 index 00000000000..ee99b65d964 --- /dev/null +++ b/changelogs/unreleased/50101-empty-state-component.yml @@ -0,0 +1,5 @@ +--- +title: Creates empty state vue component for job view +merge_request: +author: +type: other diff --git a/spec/javascripts/jobs/empty_state_spec.js b/spec/javascripts/jobs/empty_state_spec.js new file mode 100644 index 00000000000..f8feb069fe0 --- /dev/null +++ b/spec/javascripts/jobs/empty_state_spec.js @@ -0,0 +1,90 @@ +import Vue from 'vue'; +import component from '~/jobs/components/empty_state.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Empty State', () => { + const Component = Vue.extend(component); + let vm; + + const props = { + illustrationPath: 'illustrations/pending_job_empty.svg', + illustrationSizeClass: 'svg-430', + title: 'This job has not started yet', + }; + + const content = 'This job is in pending state and is waiting to be picked by a runner'; + + afterEach(() => { + vm.$destroy(); + }); + + describe('renders image and title', () => { + beforeEach(() => { + vm = mountComponent(Component, { + ...props, + content, + }); + }); + + it('renders img with provided path and size', () => { + expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(props.illustrationPath); + expect(vm.$el.querySelector('.svg-content').classList).toContain(props.illustrationSizeClass); + }); + + it('renders provided title', () => { + expect(vm.$el.querySelector('.js-job-empty-state-title').textContent.trim()).toEqual( + props.title, + ); + }); + }); + + describe('with content', () => { + it('renders content', () => { + vm = mountComponent(Component, { + ...props, + content, + }); + + expect(vm.$el.querySelector('.js-job-empty-state-content').textContent.trim()).toEqual( + content, + ); + }); + }); + + describe('without content', () => { + it('does not render content', () => { + vm = mountComponent(Component, { + ...props, + }); + expect(vm.$el.querySelector('.js-job-empty-state-content')).toBeNull(); + }); + }); + + describe('with action', () => { + it('renders action', () => { + vm = mountComponent(Component, { + ...props, + content, + action: { + link: 'runner', + title: 'Check runner', + method: 'post', + }, + }); + + expect(vm.$el.querySelector('.js-job-empty-state-action').getAttribute('href')).toEqual( + 'runner', + ); + }); + }); + + describe('without action', () => { + it('does not render action', () => { + vm = mountComponent(Component, { + ...props, + content, + }); + expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull(); + }); + }); +}); -- cgit v1.2.1 From 4157073068ceffb25ee8d9b2401467c1dfd60da2 Mon Sep 17 00:00:00 2001 From: sliaquat <sliaquat@gitlab.com> Date: Thu, 16 Aug 2018 16:40:03 +0500 Subject: Remove some duplicate tests and fix some typos --- spec/requests/api/project_hooks_spec.rb | 5 -- spec/requests/api/project_import_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 105 ++----------------------- spec/support/api/milestones_shared_examples.rb | 8 -- 4 files changed, 6 insertions(+), 114 deletions(-) diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index bc45a63d9f1..d3f81cc038d 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -83,11 +83,6 @@ describe API::ProjectHooks, 'ProjectHooks' do expect(response).to have_gitlab_http_status(403) end end - - it "returns a 404 error if hook id is not available" do - get api("/projects/#{project.id}/hooks/1234", user) - expect(response).to have_gitlab_http_status(404) - end end describe "POST /projects/:id/hooks" do diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index e3fb6cecce9..bc06f3c3732 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -42,7 +42,7 @@ describe API::ProjectImport do expect(response).to have_gitlab_http_status(201) end - it 'does not shedule an import for a nampespace that does not exist' do + it 'does not schedule an import for a namespace that does not exist' do expect_any_instance_of(Project).not_to receive(:import_schedule) expect(::Projects::CreateService).not_to receive(:new) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index eb41750bf47..c249c881db5 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -20,7 +20,6 @@ describe API::Projects do let(:admin) { create(:admin) } let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project2) { create(:project, namespace: user.namespace) } - let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :developer, user: user3, project: project) } let(:user4) { create(:user) } let(:project3) do @@ -575,7 +574,7 @@ describe API::Projects do expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif") end - it 'sets a project as allowing outdated diff discussions to automatically resolve' do + it 'sets a project as not allowing outdated diff discussions to automatically resolve' do project = attributes_for(:project, resolve_outdated_diff_discussions: false) post api('/projects', user), project @@ -583,7 +582,7 @@ describe API::Projects do expect(json_response['resolve_outdated_diff_discussions']).to be_falsey end - it 'sets a project as allowing outdated diff discussions to automatically resolve if resolve_outdated_diff_discussions' do + it 'sets a project as allowing outdated diff discussions to automatically resolve' do project = attributes_for(:project, resolve_outdated_diff_discussions: true) post api('/projects', user), project @@ -698,7 +697,7 @@ describe API::Projects do expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id) end - it 'returns projects filetered by minimal access level' do + it 'returns projects filtered by minimal access level' do private_project1 = create(:project, :private, name: 'private_project1', creator_id: user4.id, namespace: user4.namespace) private_project2 = create(:project, :private, name: 'private_project2', creator_id: user4.id, namespace: user4.namespace) private_project1.add_developer(user2) @@ -789,7 +788,7 @@ describe API::Projects do expect(json_response['visibility']).to eq('private') end - it 'sets a project as allowing outdated diff discussions to automatically resolve' do + it 'sets a project as not allowing outdated diff discussions to automatically resolve' do project = attributes_for(:project, resolve_outdated_diff_discussions: false) post api("/projects/user/#{user.id}", admin), project @@ -1119,100 +1118,6 @@ describe API::Projects do end end - describe 'GET /projects/:id/snippets' do - before do - snippet - end - - it 'returns an array of project snippets' do - get api("/projects/#{project.id}/snippets", user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(snippet.title) - end - end - - describe 'GET /projects/:id/snippets/:snippet_id' do - it 'returns a project snippet' do - get api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(snippet.title) - end - - it 'returns a 404 error if snippet id not found' do - get api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_gitlab_http_status(404) - end - end - - describe 'POST /projects/:id/snippets' do - it 'creates a new project snippet' do - post api("/projects/#{project.id}/snippets", user), - title: 'api test', file_name: 'sample.rb', code: 'test', visibility: 'private' - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq('api test') - end - - it 'returns a 400 error if invalid snippet is given' do - post api("/projects/#{project.id}/snippets", user) - expect(status).to eq(400) - end - end - - describe 'PUT /projects/:id/snippets/:snippet_id' do - it 'updates an existing project snippet' do - put api("/projects/#{project.id}/snippets/#{snippet.id}", user), - code: 'updated code' - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('example') - expect(snippet.reload.content).to eq('updated code') - end - - it 'updates an existing project snippet with new title' do - put api("/projects/#{project.id}/snippets/#{snippet.id}", user), - title: 'other api test' - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq('other api test') - end - end - - describe 'DELETE /projects/:id/snippets/:snippet_id' do - before do - snippet - end - - it 'deletes existing project snippet' do - expect do - delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) - - expect(response).to have_gitlab_http_status(204) - end.to change { Snippet.count }.by(-1) - end - - it 'returns 404 when deleting unknown snippet id' do - delete api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_gitlab_http_status(404) - end - - it_behaves_like '412 response' do - let(:request) { api("/projects/#{project.id}/snippets/#{snippet.id}", user) } - end - end - - describe 'GET /projects/:id/snippets/:snippet_id/raw' do - it 'gets a raw project snippet' do - get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) - expect(response).to have_gitlab_http_status(200) - end - - it 'returns a 404 error if raw project snippet not found' do - get api("/projects/#{project.id}/snippets/5555/raw", user) - expect(response).to have_gitlab_http_status(404) - end - end - describe 'fork management' do let(:project_fork_target) { create(:project) } let(:project_fork_source) { create(:project, :public) } @@ -1235,7 +1140,7 @@ describe API::Projects do expect(project_fork_target.forked?).to be_truthy end - it 'refreshes the forks count cachce' do + it 'refreshes the forks count cache' do expect(project_fork_source.forks_count).to be_zero post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index a15189db35f..afd6448aa26 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -102,14 +102,6 @@ shared_examples_for 'group and project milestones' do |route_definition| expect(json_response['iid']).to eq(milestone.iid) end - it 'returns a milestone by id' do - get api(resource_route, user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['title']).to eq(milestone.title) - expect(json_response['iid']).to eq(milestone.iid) - end - it 'returns 401 error if user not authenticated' do get api(resource_route) -- cgit v1.2.1 From b7fa2e0beb51ba2f489c67fbcabaff4169816305 Mon Sep 17 00:00:00 2001 From: Steve Azzopardi <steveazz@outlook.com> Date: Thu, 19 Jul 2018 16:49:32 +0200 Subject: Add documentation for interactive web terminals Create a new page for the interactive web terminals since it doesn't fit in any other place. Add link in CI home page. --- doc/ci/README.md | 2 + .../img/finished_job_with_terminal_open.png | Bin 0 -> 40247 bytes .../img/interactive_web_terminal_page.png | Bin 0 -> 23431 bytes .../img/interactive_web_terminal_running_job.png | Bin 0 -> 26692 bytes doc/ci/interactive_web_terminal/index.md | 48 +++++++++++++++++++++ 5 files changed, 50 insertions(+) create mode 100644 doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png create mode 100644 doc/ci/interactive_web_terminal/img/interactive_web_terminal_page.png create mode 100644 doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png create mode 100644 doc/ci/interactive_web_terminal/index.md diff --git a/doc/ci/README.md b/doc/ci/README.md index 7666219acb0..d782d64e971 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -76,6 +76,8 @@ learn how to leverage its potential even more. - [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md) - [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or more Kubernetes clusters to your project +- [Interactive web terminal](interactive_web_terminal/index.md) - Open an interactive + web terminal to debug the running jobs ## GitLab CI/CD for Docker diff --git a/doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png b/doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png new file mode 100644 index 00000000000..80be16301e8 Binary files /dev/null and b/doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png differ diff --git a/doc/ci/interactive_web_terminal/img/interactive_web_terminal_page.png b/doc/ci/interactive_web_terminal/img/interactive_web_terminal_page.png new file mode 100644 index 00000000000..b59c1b6bc43 Binary files /dev/null and b/doc/ci/interactive_web_terminal/img/interactive_web_terminal_page.png differ diff --git a/doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png b/doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png new file mode 100644 index 00000000000..cc60ecdfdc9 Binary files /dev/null and b/doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png differ diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md new file mode 100644 index 00000000000..2b4048599da --- /dev/null +++ b/doc/ci/interactive_web_terminal/index.md @@ -0,0 +1,48 @@ +# Getting started with interactive web terminals + +> Introduced in GitLab 11.2. + +Interactive web terminals give the user access to a terminal in GitLab for +running one-of commands for their CI pipeline. + +NOTE: **Note:** +This is not available for the shared Runners on GitLab.com. +To make use of this feature, you need to provide your +[own Runner](https://docs.gitlab.com/runner/install/) and properly +[configure it](#configuration). + +## Configuration + +Two things need to be configured for the interactive web terminal to work: + +- The Runner needs to have [`[session_server]` configured + properly][session-server] +- Web terminals need to be + [enabled](../../administration/integration/terminal.md#enabling-and-disabling-terminal-support) + +## Debugging a running job + +NOTE: **Note:** Not all executors are +[supported](https://docs.gitlab.com/runner/executors/#compatibility-chart). + +Sometimes, when a job is running, things don't go as you would expect, and it +would be helpful if one can have a shell to aid debugging. When a job is +running, on the right panel you can see a button that will open the terminal +for the current job. + +![Example of job running with terminal +available](img/interactive_web_terminal_running_job.png) + +When clicked, you will be redirected to a new page where you can access the +terminal and type commands like a normal shell. + +![terminal of the job](img/interactive_web_terminal_page.png) + +If you have the terminal open and the job has finished with its tasks, the +terminal will block the job from finishing for the duration configured in +[`[session_server].terminal_max_retention_time`][session-server] until you +close the terminal window. + +![finished job with terminal open](img/finished_job_with_terminal_open.png) + +[session-server]: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section -- cgit v1.2.1 From e321eb6093c79e8d607b6e1cb22065cb9a48e1ff Mon Sep 17 00:00:00 2001 From: Steve Azzopardi <steveazz@outlook.com> Date: Thu, 16 Aug 2018 14:10:25 +0200 Subject: Update interactive web terminal documentation --- .../img/finished_job_with_terminal_open.png | Bin 40247 -> 35571 bytes .../img/interactive_web_terminal_running_job.png | Bin 26692 -> 55682 bytes doc/ci/interactive_web_terminal/index.md | 8 ++++---- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png b/doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png index 80be16301e8..199268a1486 100644 Binary files a/doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png and b/doc/ci/interactive_web_terminal/img/finished_job_with_terminal_open.png differ diff --git a/doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png b/doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png index cc60ecdfdc9..f92c6df07a1 100644 Binary files a/doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png and b/doc/ci/interactive_web_terminal/img/interactive_web_terminal_running_job.png differ diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md index 2b4048599da..55f43e0765b 100644 --- a/doc/ci/interactive_web_terminal/index.md +++ b/doc/ci/interactive_web_terminal/index.md @@ -1,6 +1,6 @@ # Getting started with interactive web terminals -> Introduced in GitLab 11.2. +> Introduced in GitLab 11.3. Interactive web terminals give the user access to a terminal in GitLab for running one-of commands for their CI pipeline. @@ -27,14 +27,14 @@ NOTE: **Note:** Not all executors are Sometimes, when a job is running, things don't go as you would expect, and it would be helpful if one can have a shell to aid debugging. When a job is -running, on the right panel you can see a button that will open the terminal +running, on the right panel you can see a button `debug` that will open the terminal for the current job. ![Example of job running with terminal available](img/interactive_web_terminal_running_job.png) -When clicked, you will be redirected to a new page where you can access the -terminal and type commands like a normal shell. +When clicked, a new tab will open to the terminal page where you can access +the terminal and type commands like a normal shell. ![terminal of the job](img/interactive_web_terminal_page.png) -- cgit v1.2.1 From 8d5c92c558cfb3d3850813c15d6ca5d1c2d11fdd Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Thu, 16 Aug 2018 13:53:36 +0100 Subject: Creates Vue commit component for job log page --- .../javascripts/jobs/components/commit_block.vue | 64 +++++++++++++++++++ changelogs/unreleased/50101-commit-block.yml | 5 ++ spec/javascripts/jobs/commit_block_spec.js | 73 ++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 app/assets/javascripts/jobs/components/commit_block.vue create mode 100644 changelogs/unreleased/50101-commit-block.yml create mode 100644 spec/javascripts/jobs/commit_block_spec.js diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue new file mode 100644 index 00000000000..7f485295513 --- /dev/null +++ b/app/assets/javascripts/jobs/components/commit_block.vue @@ -0,0 +1,64 @@ +<script> +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + +export default { + components: { + ClipboardButton, + }, + props: { + pipelineShortSha: { + type: String, + required: true, + }, + pipelineShaPath: { + type: String, + required: true, + }, + mergeRequestReference: { + type: String, + required: false, + default: null, + }, + mergeRequestPath: { + type: String, + required: false, + default: null, + }, + gitCommitTitlte: { + type: String, + required: true, + }, + }, +}; +</script> +<template> + <div class="block"> + <p> + {{ __('Commit') }} + + <a + :href="pipelineShaPath" + class="js-commit-sha commit-sha link-commit" + > + {{ pipelineShortSha }} + </a> + + <clipboard-button + :text="pipelineShortSha" + :title="__('Copy commit SHA to clipboard')" + /> + + <a + v-if="mergeRequestPath && mergeRequestReference" + :href="mergeRequestPath" + class="js-link-commit link-commit" + > + {{ mergeRequestReference }} + </a> + </p> + + <p class="build-light-text append-bottom-0"> + {{ gitCommitTitlte }} + </p> + </div> +</template> diff --git a/changelogs/unreleased/50101-commit-block.yml b/changelogs/unreleased/50101-commit-block.yml new file mode 100644 index 00000000000..f6bad4c8154 --- /dev/null +++ b/changelogs/unreleased/50101-commit-block.yml @@ -0,0 +1,5 @@ +--- +title: Creates vue component for commit block in job log page +merge_request: +author: +type: other diff --git a/spec/javascripts/jobs/commit_block_spec.js b/spec/javascripts/jobs/commit_block_spec.js new file mode 100644 index 00000000000..f755a5042b5 --- /dev/null +++ b/spec/javascripts/jobs/commit_block_spec.js @@ -0,0 +1,73 @@ +import Vue from 'vue'; +import component from '~/jobs/components/commit_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Commit block', () => { + const Component = Vue.extend(component); + let vm; + + const props = { + pipelineShortSha: '1f0fb84f', + pipelineShaPath: 'commit/1f0fb84fb6770d74d97eee58118fd3909cd4f48c', + mergeRequestReference: '!21244', + mergeRequestPath: 'merge_requests/21244', + gitCommitTitlte: 'Regenerate pot files', + }; + + afterEach(() => { + vm.$destroy(); + }); + + describe('pipeline short sha', () => { + beforeEach(() => { + vm = mountComponent(Component, { + ...props, + }); + }); + + it('renders pipeline short sha link', () => { + expect(vm.$el.querySelector('.js-commit-sha').getAttribute('href')).toEqual(props.pipelineShaPath); + expect(vm.$el.querySelector('.js-commit-sha').textContent.trim()).toEqual(props.pipelineShortSha); + }); + + it('renders clipboard button', () => { + expect(vm.$el.querySelector('button').getAttribute('data-clipboard-text')).toEqual(props.pipelineShortSha); + }); + }); + + describe('with merge request', () => { + it('renders merge request link and reference', () => { + vm = mountComponent(Component, { + ...props, + }); + + expect(vm.$el.querySelector('.js-link-commit').getAttribute('href')).toEqual(props.mergeRequestPath); + expect(vm.$el.querySelector('.js-link-commit').textContent.trim()).toEqual(props.mergeRequestReference); + + }); + }); + + describe('without merge request', () => { + it('does not render merge request', () => { + const copyProps = Object.assign({}, props); + delete copyProps.mergeRequestPath; + delete copyProps.mergeRequestReference; + + vm = mountComponent(Component, { + ...copyProps, + }); + + expect(vm.$el.querySelector('.js-link-commit')).toBeNull(); + }); + }); + + describe('git commit title', () => { + it('renders git commit title', () => { + vm = mountComponent(Component, { + ...props, + }); + + expect(vm.$el.textContent).toContain(props.gitCommitTitlte); + }); + }); +}); -- cgit v1.2.1 From 0ade3a0755ac6172b679ccb8b788dd76fe54b19e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Thu, 16 Aug 2018 12:07:59 +0100 Subject: Creates Vue component for trigger variables block in job log page Regenerate pot files Remove `#` Remove empty lines --- .../javascripts/jobs/components/trigger_block.vue | 84 ++++++++++++++++++++++ changelogs/unreleased/50101-trigger.yml | 5 ++ locale/gitlab.pot | 6 ++ spec/javascripts/jobs/trigger_value_spec.js | 66 +++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 app/assets/javascripts/jobs/components/trigger_block.vue create mode 100644 changelogs/unreleased/50101-trigger.yml create mode 100644 spec/javascripts/jobs/trigger_value_spec.js diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue new file mode 100644 index 00000000000..8a88e5da6aa --- /dev/null +++ b/app/assets/javascripts/jobs/components/trigger_block.vue @@ -0,0 +1,84 @@ +<script> + export default { + props: { + shortToken: { + type: String, + required: false, + default: null, + }, + + variables: { + type: Object, + required: false, + default: () => ({}), + }, + }, + data() { + return { + areVariablesVisible: false, + }; + }, + computed: { + hasVariables() { + return Object.keys(this.variables).length > 0; + }, + }, + methods: { + revealVariables() { + this.areVariablesVisible = true; + }, + }, + }; +</script> + +<template> + <div class="build-widget block"> + <h4 class="title"> + {{ __('Trigger') }} + </h4> + + <p + v-if="shortToken" + class="js-short-token" + > + <span class="build-light-text"> + {{ __('Token') }} + </span> + {{ shortToken }} + </p> + + <p v-if="hasVariables"> + <button + type="button" + class="btn btn-default group js-reveal-variables" + @click="revealVariables" + > + {{ __('Reveal Variables') }} + </button> + + </p> + + <dl + v-if="areVariablesVisible" + class="js-build-variables trigger-build-variables" + > + <template + v-for="(value, key) in variables" + > + <dt + :key="`${key}-variable`" + class="js-build-variable trigger-build-variable" + > + {{ key }} + </dt> + + <dd + :key="`${key}-value`" + class="js-build-value trigger-build-value" + > + {{ value }} + </dd> + </template> + </dl> + </div> +</template> diff --git a/changelogs/unreleased/50101-trigger.yml b/changelogs/unreleased/50101-trigger.yml new file mode 100644 index 00000000000..df4243afa63 --- /dev/null +++ b/changelogs/unreleased/50101-trigger.yml @@ -0,0 +1,5 @@ +--- +title: Creates Vue component for trigger variables block in job log page +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b01a0068694..f00dce06bac 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4649,6 +4649,9 @@ msgstr "" msgid "Retry verification" msgstr "" +msgid "Reveal Variables" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -5784,6 +5787,9 @@ msgstr "" msgid "Trending" msgstr "" +msgid "Trigger" +msgstr "" + msgid "Trigger this manual action" msgstr "" diff --git a/spec/javascripts/jobs/trigger_value_spec.js b/spec/javascripts/jobs/trigger_value_spec.js new file mode 100644 index 00000000000..acf91510ed2 --- /dev/null +++ b/spec/javascripts/jobs/trigger_value_spec.js @@ -0,0 +1,66 @@ +import Vue from 'vue'; +import component from '~/jobs/components/trigger_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Trigger block', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('with short token', () => { + it('renders short token', () => { + vm = mountComponent(Component, { + shortToken: '0a666b2', + }); + + expect(vm.$el.querySelector('.js-short-token').textContent).toContain('0a666b2'); + }); + }); + + describe('without short token', () => { + it('does not render short token', () => { + vm = mountComponent(Component, {}); + + expect(vm.$el.querySelector('.js-short-token')).toBeNull(); + }); + }); + + describe('with variables', () => { + describe('reveal variables', () => { + it('reveals variables on click', done => { + vm = mountComponent(Component, { + variables: { + key: 'value', + variable: 'foo', + }, + }); + + vm.$el.querySelector('.js-reveal-variables').click(); + + vm + .$nextTick() + .then(() => { + expect(vm.$el.querySelector('.js-build-variables')).not.toBeNull(); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('key'); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('value'); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('variable'); + expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('foo'); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('without variables', () => { + it('does not render variables', () => { + vm = mountComponent(Component); + + expect(vm.$el.querySelector('.js-reveal-variables')).toBeNull(); + expect(vm.$el.querySelector('.js-build-variables')).toBeNull(); + }); + }); +}); -- cgit v1.2.1 From caaadb87b1825417065f843ae65b8afd9367310c Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg <git@zjvandeweg.nl> Date: Tue, 14 Aug 2018 11:40:10 +0200 Subject: Remove feature gates for Repository Languages The repository languages feature got introduced through: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19480. This MR included feature flags to guard against performance regressions, which allowed GitLab to test it on gitlab.com before the customers would be exposed. .com worked out fine, as shown by our internal monitoring tools, which prompts feature gate removal. Given the docs weren't present yet, these were added too. --- app/views/projects/show.html.haml | 3 +-- app/workers/detect_repository_languages_worker.rb | 2 -- .../project/repository/img/repository_languages.png | Bin 0 -> 88244 bytes doc/user/project/repository/index.md | 10 ++++++++++ .../detect_repository_languages_service_spec.rb | 4 ---- 5 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 doc/user/project/repository/img/repository_languages.png diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 8a5abb64515..df8a5742450 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -21,8 +21,7 @@ %nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] } = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout) = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout) - - if Feature.enabled?(:repository_languages, @project.namespace.becomes(Namespace)) - = repository_languages_bar(@project.repository_languages) + = repository_languages_bar(@project.repository_languages) %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } - if @project.archived? diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb index 537b8fd5963..081c0d42c27 100644 --- a/app/workers/detect_repository_languages_worker.rb +++ b/app/workers/detect_repository_languages_worker.rb @@ -14,8 +14,6 @@ class DetectRepositoryLanguagesWorker user = User.find_by(id: user_id) return unless project && user - return if Feature.disabled?(:repository_languages, project.namespace) - try_obtain_lease do ::Projects::DetectRepositoryLanguagesService.new(project, user).execute end diff --git a/doc/user/project/repository/img/repository_languages.png b/doc/user/project/repository/img/repository_languages.png new file mode 100644 index 00000000000..d9fb1278e06 Binary files /dev/null and b/doc/user/project/repository/img/repository_languages.png differ diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 704c1777e62..1c3915a5fdd 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -155,6 +155,16 @@ The repository graph displays visually the Git flow strategy used in that reposi Find it under your project's **Repository > Graph**. +## Repository Languages + +For the default branch of each repository, GitLab will determine what programming languages +were used and display this on the projects pages. + +![Repository Languages bar](img/repository_languages.png) + +Not all files are detected, among others; documentation, +vendored code, and most markup languages are excluded. + ## Compare Select branches to compare and view the changes inline: diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb index f90d558938f..deea1189cdf 100644 --- a/spec/services/projects/detect_repository_languages_service_spec.rb +++ b/spec/services/projects/detect_repository_languages_service_spec.rb @@ -5,10 +5,6 @@ describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_ subject { described_class.new(project, project.owner) } - before do - allow(Feature).to receive(:disabled?).and_return(false) - end - describe '#execute' do context 'without previous detection' do it 'inserts new programming languages in the database' do -- cgit v1.2.1 From 555cdadee69cb65768cd3655830f980194e9e607 Mon Sep 17 00:00:00 2001 From: Dylan Griffith <dyl.griffith@gmail.com> Date: Thu, 16 Aug 2018 13:24:25 +0000 Subject: Resolve "Enable Auto DevOps instance-wide on GitLab.com" --- app/models/project.rb | 8 +++--- app/models/project_auto_devops.rb | 6 +---- app/services/projects/update_service.rb | 2 +- doc/topics/autodevops/index.md | 5 ++++ spec/models/project_auto_devops_spec.rb | 6 ++--- spec/models/project_spec.rb | 48 ++++++++++++++++++++++----------- spec/spec_helper.rb | 7 +++++ 7 files changed, 54 insertions(+), 28 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 7735f23cb9e..94c1d60f071 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -27,6 +27,7 @@ class Project < ActiveRecord::Base include FastDestroyAll::Helpers include WithUploads include BatchDestroyDependentAssociations + include FeatureGate extend Gitlab::Cache::RequestCache extend Gitlab::ConfigHelper @@ -519,18 +520,19 @@ class Project < ActiveRecord::Base def auto_devops_enabled? if auto_devops&.enabled.nil? - Gitlab::CurrentSettings.auto_devops_enabled? + has_auto_devops_implicitly_enabled? else auto_devops.enabled? end end def has_auto_devops_implicitly_enabled? - auto_devops&.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled? + auto_devops&.enabled.nil? && + (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self)) end def has_auto_devops_implicitly_disabled? - auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled? + auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self)) end def empty_repo? diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 155400d1a43..dc6736dd9cd 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -47,12 +47,8 @@ class ProjectAutoDevops < ActiveRecord::Base end def needs_to_create_deploy_token? - auto_devops_enabled? && + project.auto_devops_enabled? && !project.public? && !project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present? end - - def auto_devops_enabled? - Gitlab::CurrentSettings.auto_devops_enabled? || enabled? - end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 97f181ccea8..e390d7a04c3 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -30,7 +30,7 @@ module Projects def run_auto_devops_pipeline? return false if project.repository.gitlab_ci_yml || !project.auto_devops&.previous_changes&.include?('enabled') - project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?) + project.auto_devops_enabled? end private diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index f5574506595..0474182e324 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -233,6 +233,11 @@ in **Admin Area > Settings > Continuous Integration and Deployment**. Doing that all the projects that haven't explicitly set an option will have Auto DevOps enabled by default. +NOTE: **Note:** +There is also a feature flag to enable Auto DevOps to a percentage of projects +which can be enabled from the console with +`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`. + ### Deployment strategy > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38542) in GitLab 11.0. diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 749b2094787..797d767465a 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -100,7 +100,7 @@ describe ProjectAutoDevops do end end - describe '#set_gitlab_deploy_token' do + describe '#create_gitlab_deploy_token' do let(:auto_devops) { build(:project_auto_devops, project: project) } context 'when the project is public' do @@ -144,9 +144,9 @@ describe ProjectAutoDevops do end end - context 'when autodevops is enabled at instancel level' do + context 'when autodevops is enabled at instance level' do let(:project) { create(:project, :repository, :internal) } - let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) } + let(:auto_devops) { build(:project_auto_devops, enabled: nil, project: project) } it 'should create a deploy token' do allow(Gitlab::CurrentSettings).to receive(:auto_devops_enabled?).and_return(true) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 076de06cf99..d8a5e5f6869 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3259,6 +3259,11 @@ describe Project do end describe '#auto_devops_enabled?' do + before do + allow(Feature).to receive(:enabled?).and_call_original + Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0) + end + set(:project) { create(:project) } subject { project.auto_devops_enabled? } @@ -3268,19 +3273,14 @@ describe Project do stub_application_setting(auto_devops_enabled: true) end - it 'auto devops is implicitly enabled' do - expect(project.auto_devops).to be_nil - expect(project).to be_auto_devops_enabled - end + it { is_expected.to be_truthy } context 'when explicitly enabled' do before do create(:project_auto_devops, project: project) end - it "auto devops is enabled" do - expect(project).to be_auto_devops_enabled - end + it { is_expected.to be_truthy } end context 'when explicitly disabled' do @@ -3288,9 +3288,7 @@ describe Project do create(:project_auto_devops, project: project, enabled: false) end - it "auto devops is disabled" do - expect(project).not_to be_auto_devops_enabled - end + it { is_expected.to be_falsey } end end @@ -3299,19 +3297,22 @@ describe Project do stub_application_setting(auto_devops_enabled: false) end - it 'auto devops is implicitly disabled' do - expect(project.auto_devops).to be_nil - expect(project).not_to be_auto_devops_enabled - end + it { is_expected.to be_falsey } context 'when explicitly enabled' do before do create(:project_auto_devops, project: project) end - it "auto devops is enabled" do - expect(project).to be_auto_devops_enabled + it { is_expected.to be_truthy } + end + + context 'when force_autodevops_on_by_default is enabled for the project' do + before do + Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100) end + + it { is_expected.to be_truthy } end end end @@ -3361,6 +3362,11 @@ describe Project do end describe '#has_auto_devops_implicitly_disabled?' do + before do + allow(Feature).to receive(:enabled?).and_call_original + Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0) + end + set(:project) { create(:project) } context 'when enabled in settings' do @@ -3382,6 +3388,16 @@ describe Project do expect(project).to have_auto_devops_implicitly_disabled end + context 'when force_autodevops_on_by_default is enabled for the project' do + before do + Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100) + end + + it 'does not have auto devops implicitly disabled' do + expect(project).not_to have_auto_devops_implicitly_disabled + end + end + context 'when explicitly disabled' do before do create(:project_auto_devops, project: project, enabled: false) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bd564cc60a6..f4441a6b700 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -111,6 +111,13 @@ RSpec.configure do |config| config.before(:example) do # Enable all features by default for testing allow(Feature).to receive(:enabled?) { true } + + # The following can be removed when we remove the staged rollout strategy + # and we can just enable it using instance wide settings + # (ie. ApplicationSetting#auto_devops_enabled) + allow(Feature).to receive(:enabled?) + .with(:force_autodevops_on_by_default, anything) + .and_return(false) end config.before(:example, :request_store) do -- cgit v1.2.1 From 322dfb45c75b820cbc0b930d6e62fdfa39247996 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Thu, 16 Aug 2018 13:36:29 +0000 Subject: Creates a vue component for the top bar with controllers in job log view --- .../jobs/components/job_log_controllers.vue | 139 +++++++++++++ .../unreleased/50101-truncated-job-information.yml | 5 + locale/gitlab.pot | 18 ++ .../jobs/components/job_log_controllers_spec.js | 217 +++++++++++++++++++++ 4 files changed, 379 insertions(+) create mode 100644 app/assets/javascripts/jobs/components/job_log_controllers.vue create mode 100644 changelogs/unreleased/50101-truncated-job-information.yml create mode 100644 spec/javascripts/jobs/components/job_log_controllers_spec.js diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue new file mode 100644 index 00000000000..513851e376f --- /dev/null +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -0,0 +1,139 @@ +<script> + import Icon from '~/vue_shared/components/icon.vue'; + import tooltip from '~/vue_shared/directives/tooltip'; + import { numberToHumanSize } from '~/lib/utils/number_utils'; + import { s__, sprintf } from '~/locale'; + + export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + props: { + canEraseJob: { + type: Boolean, + required: true, + }, + size: { + type: Number, + required: true, + }, + rawTracePath: { + type: String, + required: false, + default: null, + }, + canScrollToTop: { + type: Boolean, + required: true, + }, + canScrollToBottom: { + type: Boolean, + required: true, + }, + }, + computed: { + jobLogSize() { + return sprintf('Showing last %{startSpanTag} %{size} %{endSpanTag} of log -', { + startSpanTag: '<span class="s-truncated-info-size truncated-info-size">', + endSpanTag: '</span>', + size: numberToHumanSize(this.size), + }); + }, + }, + methods: { + handleEraseJobClick() { + // eslint-disable-next-line no-alert + if (window.confirm(s__('Job|Are you sure you want to erase this job?'))) { + this.$emit('eraseJob'); + } + }, + handleScrollToTop() { + this.$emit('scrollJobLogTop'); + }, + handleScrollToBottom() { + this.$emit('scrollJobLogBottom'); + }, + }, + }; +</script> +<template> + <div class="top-bar"> + <!-- truncate information --> + <div class="js-truncated-info truncated-info d-none d-sm-block float-left"> + <p v-html="jobLogSize"></p> + + <a + v-if="rawTracePath" + :href="rawTracePath" + class="js-raw-link raw-link" + > + {{ s__("Job|Complete Raw") }} + </a> + </div> + <!-- eo truncate information --> + + <div class="controllers float-right"> + <!-- links --> + <a + v-tooltip + v-if="rawTracePath" + :title="s__('Job|Show complete raw')" + :href="rawTracePath" + class="js-raw-link-controller controllers-buttons" + data-container="body" + > + <icon name="doc-text" /> + </a> + + <button + v-tooltip + v-if="canEraseJob" + :title="s__('Job|Erase job log')" + type="button" + class="js-erase-link controllers-buttons" + data-container="body" + @click="handleEraseJobClick" + > + <icon name="remove" /> + </button> + <!-- eo links --> + + <!-- scroll buttons --> + <div + v-tooltip + :title="s__('Job|Scroll to top')" + class="controllers-buttons" + data-container="body" + > + <button + :disabled="!canScrollToTop" + type="button" + class="js-scroll-top btn-scroll btn-transparent btn-blank" + @click="handleScrollToTop" + > + <icon name="scroll_up"/> + </button> + </div> + + <div + v-tooltip + :title="s__('Job|Scroll to bottom')" + class="controllers-buttons" + data-container="body" + > + <button + :disabled="!canScrollToBottom" + type="button" + class="js-scroll-bottom btn-scroll btn-transparent btn-blank" + @click="handleScrollToBottom" + > + <icon name="scroll_down"/> + </button> + </div> + <!-- eo scroll buttons --> + </div> + </div> +</template> diff --git a/changelogs/unreleased/50101-truncated-job-information.yml b/changelogs/unreleased/50101-truncated-job-information.yml new file mode 100644 index 00000000000..b873b8b7bf6 --- /dev/null +++ b/changelogs/unreleased/50101-truncated-job-information.yml @@ -0,0 +1,5 @@ +--- +title: Creates vue component for job log top bar with controllers +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b01a0068694..b370cc13f11 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3139,12 +3139,21 @@ msgstr "" msgid "Jobs" msgstr "" +msgid "Job|Are you sure you want to erase this job?" +msgstr "" + msgid "Job|Browse" msgstr "" +msgid "Job|Complete Raw" +msgstr "" + msgid "Job|Download" msgstr "" +msgid "Job|Erase job log" +msgstr "" + msgid "Job|Job artifacts" msgstr "" @@ -3157,6 +3166,15 @@ msgstr "" msgid "Job|Keep" msgstr "" +msgid "Job|Scroll to bottom" +msgstr "" + +msgid "Job|Scroll to top" +msgstr "" + +msgid "Job|Show complete raw" +msgstr "" + msgid "Job|The artifacts were removed" msgstr "" diff --git a/spec/javascripts/jobs/components/job_log_controllers_spec.js b/spec/javascripts/jobs/components/job_log_controllers_spec.js new file mode 100644 index 00000000000..416dfab8a48 --- /dev/null +++ b/spec/javascripts/jobs/components/job_log_controllers_spec.js @@ -0,0 +1,217 @@ +import Vue from 'vue'; +import component from '~/jobs/components/job_log_controllers.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Job log controllers', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('Truncate information', () => { + + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders size information', () => { + expect(vm.$el.querySelector('.js-truncated-info').textContent).toContain('499.95 KiB'); + }); + + it('renders link to raw trace', () => { + expect(vm.$el.querySelector('.js-raw-link').getAttribute('href')).toEqual('/raw'); + }); + + }); + + describe('links section', () => { + describe('with raw trace path', () => { + it('renders raw trace link', () => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + + expect(vm.$el.querySelector('.js-raw-link-controller').getAttribute('href')).toEqual('/raw'); + }); + }); + + describe('without raw trace path', () => { + it('does not render raw trace link', () => { + vm = mountComponent(Component, { + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + + expect(vm.$el.querySelector('.js-raw-link-controller')).toBeNull(); + }); + }); + + describe('when is erasable', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders erase job button', () => { + expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); + }); + + describe('on click', () => { + describe('when user confirms action', () => { + it('emits eraseJob event', () => { + spyOn(window, 'confirm').and.returnValue(true); + spyOn(vm, '$emit'); + + vm.$el.querySelector('.js-erase-link').click(); + + expect(vm.$emit).toHaveBeenCalledWith('eraseJob'); + }); + }); + + describe('when user does not confirm action', () => { + it('does not emit eraseJob event', () => { + spyOn(window, 'confirm').and.returnValue(false); + spyOn(vm, '$emit'); + + vm.$el.querySelector('.js-erase-link').click(); + + expect(vm.$emit).not.toHaveBeenCalledWith('eraseJob'); + }); + }); + }); + }); + + describe('when it is not erasable', () => { + it('does not render erase button', () => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: false, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + + expect(vm.$el.querySelector('.js-erase-link')).toBeNull(); + }); + }); + }); + + describe('scroll buttons', () => { + describe('scroll top button', () => { + describe('when user can scroll top', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders enabled scroll top button', () => { + expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toBeNull(); + }); + + it('emits scrollJobLogTop event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-top').click(); + + expect(vm.$emit).toHaveBeenCalledWith('scrollJobLogTop'); + }); + }); + + describe('when user can not scroll top', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: false, + canScrollToBottom: true, + }); + }); + + it('renders disabled scroll top button', () => { + expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toEqual('disabled'); + }); + + it('does not emit scrollJobLogTop event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-top').click(); + + expect(vm.$emit).not.toHaveBeenCalledWith('scrollJobLogTop'); + }); + }); + }); + + describe('scroll bottom button', () => { + describe('when user can scroll bottom', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders enabled scroll bottom button', () => { + expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toBeNull(); + }); + + it('emits scrollJobLogBottom event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-bottom').click(); + + expect(vm.$emit).toHaveBeenCalledWith('scrollJobLogBottom'); + }); + }); + + describe('when user can not scroll bottom', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: false, + }); + }); + + it('renders disabled scroll bottom button', () => { + expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toEqual('disabled'); + + }); + + it('does not emit scrollJobLogBottom event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-bottom').click(); + + expect(vm.$emit).not.toHaveBeenCalledWith('scrollJobLogBottom'); + }); + }); + }); + }); +}); + -- cgit v1.2.1 From 79e6535a6781a403f0c59824691a202781fb505f Mon Sep 17 00:00:00 2001 From: Shinya Maeda <shinya@gitlab.com> Date: Thu, 16 Aug 2018 14:25:44 +0000 Subject: Add ci_archive_traces_cron_worker configuration to gitlab.yml.example --- .../unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml | 5 +++++ config/gitlab.yml.example | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 changelogs/unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml diff --git a/changelogs/unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml b/changelogs/unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml new file mode 100644 index 00000000000..d963dc5bac3 --- /dev/null +++ b/changelogs/unreleased/add-ci_archive_traces_cron_worker-to-gitlab-yml.yml @@ -0,0 +1,5 @@ +--- +title: Add an example of the configuration of archive trace cron worker in gitlab.yml.example +merge_request: 20583 +author: +type: other diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 561ff57c9fb..4847a82236b 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -261,6 +261,9 @@ production: &base # once per hour you will have concurrent 'git fsck' jobs. repository_check_worker: cron: "20 * * * *" + # Archive live traces which have not been archived yet + ci_archive_traces_cron_worker: + cron: "17 * * * *" # Send admin emails once a week admin_email_worker: cron: "0 0 * * 0" -- cgit v1.2.1 From 21a2dde0240bffd72c6cd19687e7d0fadcc39cf6 Mon Sep 17 00:00:00 2001 From: Luke Bennett <lbennett@gitlab.com> Date: Thu, 16 Aug 2018 14:27:21 +0000 Subject: Instance statistics docs --- .../cohorts/_cohorts_table.html.haml | 2 +- .../_no_data.html.haml | 2 +- .../index.html.haml | 2 +- doc/administration/index.md | 2 -- doc/user/admin_area/img/cohorts.png | Bin 439635 -> 0 bytes doc/user/admin_area/monitoring/convdev.md | 32 +++-------------- .../admin_area/monitoring/img/convdev_index.png | Bin 119743 -> 0 bytes doc/user/admin_area/settings/usage_statistics.md | 13 +++++-- doc/user/admin_area/user_cohorts.md | 40 +++------------------ doc/user/index.md | 4 +++ doc/user/instance_statistics/convdev.md | 26 ++++++++++++++ doc/user/instance_statistics/img/cohorts.png | Bin 0 -> 59494 bytes doc/user/instance_statistics/img/convdev_index.png | Bin 0 -> 316893 bytes .../img/instance_statistics_button.png | Bin 0 -> 9462 bytes doc/user/instance_statistics/index.md | 19 ++++++++++ doc/user/instance_statistics/user_cohorts.md | 27 ++++++++++++++ 16 files changed, 98 insertions(+), 71 deletions(-) delete mode 100644 doc/user/admin_area/img/cohorts.png delete mode 100644 doc/user/admin_area/monitoring/img/convdev_index.png create mode 100644 doc/user/instance_statistics/convdev.md create mode 100644 doc/user/instance_statistics/img/cohorts.png create mode 100644 doc/user/instance_statistics/img/convdev_index.png create mode 100644 doc/user/instance_statistics/img/instance_statistics_button.png create mode 100644 doc/user/instance_statistics/index.md create mode 100644 doc/user/instance_statistics/user_cohorts.md diff --git a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml index 701a4e62b39..6a7c999bff3 100644 --- a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml +++ b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml @@ -3,7 +3,7 @@ User cohorts are shown for the last #{@cohorts[:months_included]} months. Only users with activity are counted in the cohort total; inactive users are counted separately. - = link_to icon('question-circle'), help_page_path('user/admin_area/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' + = link_to icon('question-circle'), help_page_path('user/instance_statistics/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank' .table-holder %table.table diff --git a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml index d69c46194b4..dd795aee135 100644 --- a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml +++ b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml @@ -4,4 +4,4 @@ %h4 Data is still calculating... %p In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index. - = link_to 'Learn more', help_page_path('user/admin_area/monitoring/convdev'), target: '_blank' + = link_to 'Learn more', help_page_path('user/instance_statistics/convdev'), target: '_blank' diff --git a/app/views/instance_statistics/conversational_development_index/index.html.haml b/app/views/instance_statistics/conversational_development_index/index.html.haml index e3d1aa31dc2..dd63b98376f 100644 --- a/app/views/instance_statistics/conversational_development_index/index.html.haml +++ b/app/views/instance_statistics/conversational_development_index/index.html.haml @@ -19,7 +19,7 @@ index %br score - = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/admin_area/monitoring/convdev') + = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/instance_statistics/convdev') .convdev-cards.board-card-container - @metric.cards.each do |card| diff --git a/doc/administration/index.md b/doc/administration/index.md index 112d14652af..030a2f95e23 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -93,7 +93,6 @@ created in snippets, wikis, and repos. - [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a basic Postfix mail server with IMAP authentication on Ubuntu for incoming emails. -- [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time. [reply by email]: reply_by_email.md [issues by email]: ../user/project/issues/create_new_issue.md#new-issue-via-email @@ -137,7 +136,6 @@ created in snippets, wikis, and repos. - [Monitoring uptime](../user/admin_area/monitoring/health_check.md): Check the server status using the health check endpoint. - [IP whitelist](monitoring/ip_whitelist.md): Monitor endpoints that provide health check information when probed. - [Monitoring GitHub imports](monitoring/github_imports.md): GitLab's GitHub Importer displays Prometheus metrics to monitor the health and progress of the importer. -- [Conversational Development (ConvDev) Index](../user/admin_area/monitoring/convdev.md): Provides an overview of your entire instance's feature usage. ### Performance Monitoring diff --git a/doc/user/admin_area/img/cohorts.png b/doc/user/admin_area/img/cohorts.png deleted file mode 100644 index 8bae7faff07..00000000000 Binary files a/doc/user/admin_area/img/cohorts.png and /dev/null differ diff --git a/doc/user/admin_area/monitoring/convdev.md b/doc/user/admin_area/monitoring/convdev.md index a98602c4d70..6ad8a5a7ff0 100644 --- a/doc/user/admin_area/monitoring/convdev.md +++ b/doc/user/admin_area/monitoring/convdev.md @@ -1,29 +1,5 @@ -# Conversational Development Index +--- +redirect_to: '../../instance_statistics/convdev.md' +--- -> [Introduced][ce-30469] in GitLab 9.3. - -Conversational Development Index (ConvDev) gives you an overview of your entire -instance's feature usage, from idea to production. It looks at your usage in the -past 30 days, averaged over the number of active users in that time period. It also -provides a lead score per feature, which is calculated based on GitLab's analysis -of top performing instances, based on [usage ping data][ping] that GitLab has -collected. Your score is compared to the lead score, expressed as a percentage. -The overall index score is an average over all your feature scores. - -![ConvDev index](img/convdev_index.png) - -The page also provides helpful links to articles and GitLab docs, to help you -improve your scores. - -Your GitLab instance's usage ping must be activated in order to use this feature. -Usage ping data is aggregated on GitLab's servers for analysis. Your usage -information is **not sent** to any other GitLab instances. - -If you have just started using GitLab, it may take a few weeks for data to be -collected before this feature is available. - -This feature is accessible only to a system admin, at -**Admin area > Overview > ConvDev Index**. - -[ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469 -[ping]: ../settings/usage_statistics.md#usage-ping +This document was moved to [another location](../../instance_statistics/convdev.md). diff --git a/doc/user/admin_area/monitoring/img/convdev_index.png b/doc/user/admin_area/monitoring/img/convdev_index.png deleted file mode 100644 index 1bf1d6a83c9..00000000000 Binary files a/doc/user/admin_area/monitoring/img/convdev_index.png and /dev/null differ diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md index 381efdf5d67..b7427592e10 100644 --- a/doc/user/admin_area/settings/usage_statistics.md +++ b/doc/user/admin_area/settings/usage_statistics.md @@ -6,7 +6,7 @@ to perform various actions. All statistics are opt-out, you can enable/disable them from the admin panel under **Admin area > Settings > Usage statistics**. -## Version check +## Version check **[CORE ONLY]** If enabled, version check will inform you if a new version is available and the importance of it through a status. This is shown on the help page (i.e. `/help`) @@ -29,7 +29,7 @@ secure. If you disable version check, this information will not be collected. Enable or disable the version check at **Admin area > Settings > Usage statistics**. -## Usage ping +## Usage ping **[CORE ONLY]** > [Introduced][ee-557] in GitLab Enterprise Edition 8.10. More statistics [were added][ee-735] in GitLab Enterprise Edition @@ -67,6 +67,15 @@ production: &base usage_ping_enabled: false ``` +## Instance statistics visibility **[CORE ONLY]** + +Once usage ping is enabled, GitLab will gather data from other instances and +will be able to show [usage statistics](../../instance_statistics/index.md) +of your instance to your users. + +This can be restricted to admins by selecting "Only admins" in the Instance +Statistics visibility section under **Admin area > Settings > Usage statistics**. + [ee-557]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/557 [ee-735]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/735 [ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361 diff --git a/doc/user/admin_area/user_cohorts.md b/doc/user/admin_area/user_cohorts.md index e25e7a8bbc3..21e61e2ec44 100644 --- a/doc/user/admin_area/user_cohorts.md +++ b/doc/user/admin_area/user_cohorts.md @@ -1,37 +1,5 @@ -# Cohorts +--- +redirect_to: '../instance_statistics/user_cohorts.md' +--- -> **Notes:** -> [Introduced][ce-23361] in GitLab 9.1. - -As a benefit of having the [usage ping active](settings/usage_statistics.md), -GitLab lets you analyze the users' activities of your GitLab installation. -Under `/admin/cohorts`, when the usage ping is active, GitLab will show the -monthly cohorts of new users and their activities over time. - -## Overview - -How do we read the user cohorts table? Let's take an example with the following -user cohorts. - -![User cohort example](img/cohorts.png) - -For the cohort of June 2016, 163 users have been added on this server and have -been active since this month. One month later, in July 2016, out of -these 163 users, 155 users (or 95% of the June cohort) are still active. Two -months later, 139 users (or 85%) are still active. 9 months later, we can see -that only 6% of this cohort are still active. - -The Inactive users column shows the number of users who have been added during -the month, but who have never actually had any activity in the instance. - -How do we measure the activity of users? GitLab considers a user active if: - -* the user signs in -* the user has Git activity (whether push or pull). - -## Setup - -1. [Activate the usage ping](settings/usage_statistics.md) -2. Go to `/admin/cohorts` to see the user cohorts of the server - -[ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361 +This document was moved to [another location](../instance_statistics/user_cohorts.md). diff --git a/doc/user/index.md b/doc/user/index.md index 90f0e2285c3..649c0b664a5 100644 --- a/doc/user/index.md +++ b/doc/user/index.md @@ -172,3 +172,7 @@ Automate GitLab via [API](../api/README.md). ## Git and GitLab Learn what is [Git](../topics/git/index.md) and its best practices. + +## Instance statistics + +See [various statistics](instance_statistics/index.md) of your GitLab instance. diff --git a/doc/user/instance_statistics/convdev.md b/doc/user/instance_statistics/convdev.md new file mode 100644 index 00000000000..d2795e092fc --- /dev/null +++ b/doc/user/instance_statistics/convdev.md @@ -0,0 +1,26 @@ +# Conversational Development Index + +> [Introduced][ce-30469] in GitLab 9.3. + +Conversational Development Index (ConvDev) gives you an overview of your entire +instance's feature usage, from idea to production. It looks at your usage in the +past 30 days, averaged over the number of active users in that time period. It also +provides a lead score per feature, which is calculated based on GitLab's analysis +of top performing instances, based on [usage ping data][ping] that GitLab has +collected. Your score is compared to the lead score, expressed as a percentage. +The overall index score is an average over all your feature scores. + +![ConvDev index](img/convdev_index.png) + +The page also provides helpful links to articles and GitLab docs, to help you +improve your scores. + +Your GitLab instance's usage ping must be activated in order to use this feature. +Usage ping data is aggregated on GitLab's servers for analysis. Your usage +information is **not sent** to any other GitLab instances. + +If you have just started using GitLab, it may take a few weeks for data to be +collected before this feature is available. + +[ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469 +[ping]: ../admin_area/settings/usage_statistics.md#usage-ping diff --git a/doc/user/instance_statistics/img/cohorts.png b/doc/user/instance_statistics/img/cohorts.png new file mode 100644 index 00000000000..12e839e7cd2 Binary files /dev/null and b/doc/user/instance_statistics/img/cohorts.png differ diff --git a/doc/user/instance_statistics/img/convdev_index.png b/doc/user/instance_statistics/img/convdev_index.png new file mode 100644 index 00000000000..191295c918b Binary files /dev/null and b/doc/user/instance_statistics/img/convdev_index.png differ diff --git a/doc/user/instance_statistics/img/instance_statistics_button.png b/doc/user/instance_statistics/img/instance_statistics_button.png new file mode 100644 index 00000000000..6104321b1a6 Binary files /dev/null and b/doc/user/instance_statistics/img/instance_statistics_button.png differ diff --git a/doc/user/instance_statistics/index.md b/doc/user/instance_statistics/index.md new file mode 100644 index 00000000000..a4eca89b7fe --- /dev/null +++ b/doc/user/instance_statistics/index.md @@ -0,0 +1,19 @@ +# Instance statistics + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41416) +in GitLab 11.2. + +Instance statistics gives users or admins access to instance-wide analytics. +They are accessible to all users by default (GitLab admins can restrict its +visibility in the [admin area](../admin_area/settings/usage_statistics.md)), +and can be accessed via the top bar. + +![Instance Statistics button](img/instance_statistics_button.png) + +For the statistics to show up, [usage ping must be enabled](../admin_area/settings/usage_statistics.md#usage-ping) +by an admin in the admin settings area. + +There are two kinds of statistics: + +- [Conversational Development (ConvDev) Index](convdev.md): Provides an overview of your entire instance's feature usage. +- [User Cohorts](user_cohorts.md): Display the monthly cohorts of new users and their activities over time. diff --git a/doc/user/instance_statistics/user_cohorts.md b/doc/user/instance_statistics/user_cohorts.md new file mode 100644 index 00000000000..70d5912dc4e --- /dev/null +++ b/doc/user/instance_statistics/user_cohorts.md @@ -0,0 +1,27 @@ +# Cohorts + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/23361) +in GitLab 9.1. + +As a benefit of having the [usage ping active](../admin_area/settings/usage_statistics.md), +GitLab lets you analyze the users' activities over time of your GitLab installation. + +## Overview + +How do we read the user cohorts table? Let's take an example with the following +user cohorts. + +![User cohort example](img/cohorts.png) + +For the cohort of Jan 2018, 15 users have been added on this server and have +been active since this month. One month later, in Feb 2018, all 15 users are +still active. 6 months later (Month 6, July), we can see 10 users from this cohort +are active, or 66% of the original cohort of 15 that joined in January. + +The Inactive users column shows the number of users who have been added during +the month, but who have never actually had any activity in the instance. + +How do we measure the activity of users? GitLab considers a user active if: + +* the user signs in +* the user has Git activity (whether push or pull). -- cgit v1.2.1 From 63091cfe6432b2290f6ccd0c1e8105c8900b9df5 Mon Sep 17 00:00:00 2001 From: Shinya Maeda <shinya@gitlab.com> Date: Thu, 16 Aug 2018 14:28:47 +0000 Subject: Add rake command to migrate archived traces from local storage to object storage --- app/models/ci/build.rb | 5 + ...o-migrate-locally-persisted-archived-traces.yml | 5 + doc/administration/job_traces.md | 54 ++++++++-- lib/tasks/gitlab/traces.rake | 17 ++++ spec/tasks/gitlab/traces_rake_spec.rb | 112 ++++++++++++++++----- 5 files changed, 159 insertions(+), 34 deletions(-) create mode 100644 changelogs/unreleased/add-rake-command-to-migrate-locally-persisted-archived-traces.yml diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 3c69677baf0..faa160ad6ba 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -67,6 +67,10 @@ module Ci '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) end + scope :with_archived_trace, ->() do + where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace) + end + scope :without_archived_trace, ->() do where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace) end @@ -77,6 +81,7 @@ module Ci end scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } + scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) } scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } diff --git a/changelogs/unreleased/add-rake-command-to-migrate-locally-persisted-archived-traces.yml b/changelogs/unreleased/add-rake-command-to-migrate-locally-persisted-archived-traces.yml new file mode 100644 index 00000000000..b82344e3c9c --- /dev/null +++ b/changelogs/unreleased/add-rake-command-to-migrate-locally-persisted-archived-traces.yml @@ -0,0 +1,5 @@ +--- +title: Add rake command to migrate archived traces from local storage to object storage +merge_request: 21193 +author: +type: added diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md index 24d1a3fd151..6e2f67f61bc 100644 --- a/doc/administration/job_traces.md +++ b/doc/administration/job_traces.md @@ -3,10 +3,6 @@ Job traces are sent by GitLab Runner while it's processing a job. You can see traces in job pages, pipelines, email notifications, etc. -There isn't a way to automatically expire old job logs, but it's safe to remove -them if they're taking up too much space. If you remove the logs manually, the -job output in the UI will be empty. - ## Data flow In general, there are two states in job traces: "live trace" and "archived trace". @@ -57,11 +53,55 @@ To change the location where the job logs will be stored, follow the steps below ## Uploading traces to object storage -An archived trace is considered as a [job artifact](job_artifacts.md). -Therefore, when you [set up an object storage](job_artifacts.md#object-storage-settings), +Archived traces are considered as [job artifacts](job_artifacts.md). +Therefore, when you [set up the object storage integration](job_artifacts.md#object-storage-settings), job traces are automatically migrated to it along with the other job artifacts. -See [Data flow](#data-flow) to learn about the process. +See "Phase 4: uploading" in [Data flow](#data-flow) to learn about the process. + +## How to archive legacy job trace files + +Legacy job traces, which were created before GitLab 10.5, were not archived regularly. +It's the same state with the "2: overwriting" in the above [Data flow](#data-flow). +To archive those legacy job traces, please follow the instruction below. + +1. Execute the following command + + ```bash + gitlab-rake gitlab:traces:archive + ``` + + After you executed this task, GitLab instance queues up Sidekiq jobs (asynchronous processes) + for migrating job trace files from local storage to object storage. + It could take time to complete the all migration jobs. You can check the progress by the following command + + ```bash + sudo gitlab-rails console + ``` + + ```bash + [1] pry(main)> Sidekiq::Stats.new.queues['pipeline_background:archive_trace'] + => 100 + ``` + + If the count becomes zero, the archiving processes are done + +## How to migrate archived job traces to object storage + +If job traces have already been archived into local storage, and you want to migrate those traces to object storage, please follow the instruction below. + +1. Ensure [Object storage integration for Job Artifacts](job_artifacts.md#object-storage-settings) is enabled +1. Execute the following command + + ```bash + gitlab-rake gitlab:traces:migrate + ``` + +## How to remove job traces + +There isn't a way to automatically expire old job logs, but it's safe to remove +them if they're taking up too much space. If you remove the logs manually, the +job output in the UI will be empty. ## New live trace architecture diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake index ddcca69711f..5a232091a7e 100644 --- a/lib/tasks/gitlab/traces.rake +++ b/lib/tasks/gitlab/traces.rake @@ -18,5 +18,22 @@ namespace :gitlab do logger.info("Scheduled #{job_ids.count} jobs. From #{job_ids.min} to #{job_ids.max}") end end + + task migrate: :environment do + logger = Logger.new(STDOUT) + logger.info('Starting transfer of job traces') + + Ci::Build.joins(:project) + .with_archived_trace_stored_locally + .find_each(batch_size: 10) do |build| + begin + build.job_artifacts_trace.file.migrate!(ObjectStorage::Store::REMOTE) + + logger.info("Transferred job trace of #{build.id} to object storage") + rescue => e + logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}") + end + end + end end end diff --git a/spec/tasks/gitlab/traces_rake_spec.rb b/spec/tasks/gitlab/traces_rake_spec.rb index bd18e8ffc1e..aaf0d7242dd 100644 --- a/spec/tasks/gitlab/traces_rake_spec.rb +++ b/spec/tasks/gitlab/traces_rake_spec.rb @@ -5,51 +5,109 @@ describe 'gitlab:traces rake tasks' do Rake.application.rake_require 'tasks/gitlab/traces' end - shared_examples 'passes the job id to worker' do - it do - expect(ArchiveTraceWorker).to receive(:bulk_perform_async).with([[job.id]]) + describe 'gitlab:traces:archive' do + shared_examples 'passes the job id to worker' do + it do + expect(ArchiveTraceWorker).to receive(:bulk_perform_async).with([[job.id]]) - run_rake_task('gitlab:traces:archive') + run_rake_task('gitlab:traces:archive') + end end - end - shared_examples 'does not pass the job id to worker' do - it do - expect(ArchiveTraceWorker).not_to receive(:bulk_perform_async) + shared_examples 'does not pass the job id to worker' do + it do + expect(ArchiveTraceWorker).not_to receive(:bulk_perform_async) - run_rake_task('gitlab:traces:archive') + run_rake_task('gitlab:traces:archive') + end end - end - context 'when trace file stored in default path' do - let!(:job) { create(:ci_build, :success, :trace_live) } + context 'when trace file stored in default path' do + let!(:job) { create(:ci_build, :success, :trace_live) } - it_behaves_like 'passes the job id to worker' - end + it_behaves_like 'passes the job id to worker' + end - context 'when trace is stored in database' do - let!(:job) { create(:ci_build, :success) } + context 'when trace is stored in database' do + let!(:job) { create(:ci_build, :success) } - before do - job.update_column(:trace, 'trace in db') + before do + job.update_column(:trace, 'trace in db') + end + + it_behaves_like 'passes the job id to worker' end - it_behaves_like 'passes the job id to worker' + context 'when job has trace artifact' do + let!(:job) { create(:ci_build, :success) } + + before do + create(:ci_job_artifact, :trace, job: job) + end + + it_behaves_like 'does not pass the job id to worker' + end + + context 'when job is not finished yet' do + let!(:build) { create(:ci_build, :running, :trace_live) } + + it_behaves_like 'does not pass the job id to worker' + end end - context 'when job has trace artifact' do - let!(:job) { create(:ci_build, :success) } + describe 'gitlab:traces:migrate' do + let(:object_storage_enabled) { false } before do - create(:ci_job_artifact, :trace, job: job) + stub_artifacts_object_storage(enabled: object_storage_enabled) end - it_behaves_like 'does not pass the job id to worker' - end + subject { run_rake_task('gitlab:traces:migrate') } - context 'when job is not finished yet' do - let!(:build) { create(:ci_build, :running, :trace_live) } + let!(:job_trace) { create(:ci_job_artifact, :trace, file_store: store) } - it_behaves_like 'does not pass the job id to worker' + context 'when local storage is used' do + let(:store) { ObjectStorage::Store::LOCAL } + + context 'and job does not have file store defined' do + let(:object_storage_enabled) { true } + let(:store) { nil } + + it "migrates file to remote storage" do + subject + + expect(job_trace.reload.file_store).to eq(ObjectStorage::Store::REMOTE) + end + end + + context 'and remote storage is defined' do + let(:object_storage_enabled) { true } + + it "migrates file to remote storage" do + subject + + expect(job_trace.reload.file_store).to eq(ObjectStorage::Store::REMOTE) + end + end + + context 'and remote storage is not defined' do + it "fails to migrate to remote storage" do + subject + + expect(job_trace.reload.file_store).to eq(ObjectStorage::Store::LOCAL) + end + end + end + + context 'when remote storage is used' do + let(:object_storage_enabled) { true } + let(:store) { ObjectStorage::Store::REMOTE } + + it "file stays on remote storage" do + subject + + expect(job_trace.reload.file_store).to eq(ObjectStorage::Store::REMOTE) + end + end end end -- cgit v1.2.1 From 22060d3d27a824230a60e49dab0f56b436a9d721 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <joshua@gitlab.com> Date: Thu, 16 Aug 2018 14:32:15 +0000 Subject: Initial kaniko documentation --- doc/ci/docker/README.md | 1 + doc/ci/docker/using_kaniko.md | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 doc/ci/docker/using_kaniko.md diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md index b0e01d74f7e..8ae80b2bc02 100644 --- a/doc/ci/docker/README.md +++ b/doc/ci/docker/README.md @@ -6,3 +6,4 @@ comments: false - [Using Docker Images](using_docker_images.md) - [Using Docker Build](using_docker_build.md) +- [Using kaniko](using_kaniko.md) diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md new file mode 100644 index 00000000000..7d4f28e1f47 --- /dev/null +++ b/doc/ci/docker/using_kaniko.md @@ -0,0 +1,60 @@ +# Building images with kaniko and GitLab CI/CD + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45512) in GitLab 11.2. +Requires GitLab Runner 11.2 and above. + +[kaniko](https://github.com/GoogleContainerTools/kaniko) is a tool to build +container images from a Dockerfile, inside a container or Kubernetes cluster. + +kaniko solves two problems with using the +[docker-in-docker build](using_docker_build.md#use-docker-in-docker-executor) method: + +1. Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities) + in order to function, which is a significant security concern. +1. Docker-in-docker generally incurs a performance penalty and can be quite slow. + +## Requirements + +In order to utilize kaniko with GitLab, a [GitLab Runner](https://docs.gitlab.com/runner/) +using either the [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html), +[Docker](https://docs.gitlab.com/runner/executors/docker.html), or +[Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html) +executors is required. + +## Building a Docker image with kaniko + +When building an image with kaniko and GitLab CI/CD, you should be aware of a +few important details: + +- The kaniko debug image is recommended (`gcr.io/kaniko-project/executor:debug`) + because it has a shell, and a shell is required for an image to be used with + GitLab CI/CD. +- The entrypoint will need to be [overridden](using_docker_images.md#overriding-the-entrypoint-of-an-image), + otherwise the build script will not run. +- A Docker `config.json` file needs to be created with the authentication + information for the desired container registry. + +--- + +In the following example, kaniko is used to build a Docker image and then push +it to [GitLab Container Registry](../../user/project/container_registry.md). +The job will run only when a tag is pushed. A `config.json` file is created under +`/root/.docker` with the needed GitLab Container Registry credentials taken from the +[environment variables](../variables/README.md#predefined-variables-environment-variables) +GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the +root directory of the project, builds the Docker image and pushes it to the +project's Container Registry while tagging it with the Git tag: + +```yaml +build: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + script: + - mkdir -p /root/.docker + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + only: + - tags +``` -- cgit v1.2.1 From 2cf1e21ae48db6f932658bfc9beb04fb289b491f Mon Sep 17 00:00:00 2001 From: Phil Hughes <me@iamphill.com> Date: Thu, 16 Aug 2018 15:47:35 +0100 Subject: Fixed Web IDE live preview crashing when loading from merge request --- app/assets/javascripts/ide/components/repo_editor.vue | 1 - app/assets/javascripts/ide/stores/actions/file.js | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index f9badb01535..f55aa843444 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -133,7 +133,6 @@ export default { .then(() => this.getRawFileData({ path: this.file.path, - baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '', }), ) .then(() => { diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index c9795750d65..28b9d0df201 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -92,7 +92,7 @@ export const setFileMrChange = ({ commit }, { file, mrChange }) => { commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange }); }; -export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => { +export const getRawFileData = ({ state, commit, dispatch, getters }, { path }) => { const file = state.entries[path]; return new Promise((resolve, reject) => { service @@ -100,6 +100,9 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) = .then(raw => { if (!(file.tempFile && !file.prevPath)) commit(types.SET_FILE_RAW_DATA, { file, raw }); if (file.mrChange && file.mrChange.new_file === false) { + const baseSha = + (getters.currentMergeRequest && getters.currentMergeRequest.baseCommitSha) || ''; + service .getBaseRawFileData(file, baseSha) .then(baseRaw => { @@ -122,7 +125,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) = action: payload => dispatch('getRawFileData', payload).then(() => dispatch('setErrorMessage', null)), actionText: __('Please try again'), - actionPayload: { path, baseSha }, + actionPayload: { path }, }); reject(); }); -- cgit v1.2.1 From c4d2db1f6c4fccfd1bad9b409de06fc362dc0283 Mon Sep 17 00:00:00 2001 From: Dicker Max <dicker-95@web.de> Date: Thu, 16 Aug 2018 15:27:56 +0000 Subject: Fix if-check --- app/assets/javascripts/boards/components/board_list.vue | 2 +- ...41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml | 5 +++++ spec/javascripts/boards/board_list_spec.js | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 3e610a4088c..bfc8d9b03ad 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -203,7 +203,7 @@ export default { this.showIssueForm = !this.showIssueForm; }, onScroll() { - if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { + if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { this.loadNextPage(); } }, diff --git a/changelogs/unreleased/41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml b/changelogs/unreleased/41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml new file mode 100644 index 00000000000..bc0150c6586 --- /dev/null +++ b/changelogs/unreleased/41738-fix-sorting-issues-is-wrong-in-list-with-pagination.yml @@ -0,0 +1,5 @@ +--- +title: "Fix If-Check the result that a function was executed several times" +merge_request: 20640 +author: Max Dicker +type: fixed diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index de261d36c61..290600cf995 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -200,6 +200,15 @@ describe('Board list component', () => { }); }); + it('does not load issues if already loading', () => { + component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue(new Promise(() => {})); + + component.onScroll(); + component.onScroll(); + + expect(component.list.nextPage).toHaveBeenCalledTimes(1); + }); + it('shows loading more spinner', (done) => { component.showCount = true; component.list.loadingMore = true; -- cgit v1.2.1 From 96ce2da74ee36e88f20cbd7ceaff2ab49f0bb223 Mon Sep 17 00:00:00 2001 From: Yorick Peterse <yorickpeterse@gmail.com> Date: Thu, 16 Aug 2018 14:28:25 +0200 Subject: Blacklist the use of "destroy_all" This method usually has really bad performance implications, as it loads rows into memory and deletes them one by one. --- rubocop/cop/destroy_all.rb | 22 ++++++++++++++++++ rubocop/rubocop.rb | 1 + spec/rubocop/cop/destroy_all_spec.rb | 43 ++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 rubocop/cop/destroy_all.rb create mode 100644 spec/rubocop/cop/destroy_all_spec.rb diff --git a/rubocop/cop/destroy_all.rb b/rubocop/cop/destroy_all.rb new file mode 100644 index 00000000000..38b6cb40f91 --- /dev/null +++ b/rubocop/cop/destroy_all.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + # Cop that blacklists the use of `destroy_all`. + class DestroyAll < RuboCop::Cop::Cop + MSG = 'Use `delete_all` instead of `destroy_all`. ' \ + '`destroy_all` will load the rows into memory, then execute a ' \ + '`DELETE` for every individual row.' + + def_node_matcher :destroy_all?, <<~PATTERN + (send {send ivar lvar const} :destroy_all ...) + PATTERN + + def on_send(node) + return unless destroy_all?(node) + + add_offense(node, location: :expression) + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index a427208cdab..88c9bbf24f4 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -27,3 +27,4 @@ require_relative 'cop/project_path_helper' require_relative 'cop/rspec/env_assignment' require_relative 'cop/rspec/factories_in_migration_specs' require_relative 'cop/sidekiq_options_queue' +require_relative 'cop/destroy_all' diff --git a/spec/rubocop/cop/destroy_all_spec.rb b/spec/rubocop/cop/destroy_all_spec.rb new file mode 100644 index 00000000000..153a5a863cf --- /dev/null +++ b/spec/rubocop/cop/destroy_all_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/destroy_all' + +describe RuboCop::Cop::DestroyAll do + include CopHelper + + subject(:cop) { described_class.new } + + it 'flags the use of destroy_all with a send receiver' do + inspect_source('foo.destroy_all') + + expect(cop.offenses.size).to eq(1) + end + + it 'flags the use of destroy_all with a constant receiver' do + inspect_source('User.destroy_all') + + expect(cop.offenses.size).to eq(1) + end + + it 'flags the use of destroy_all when passing arguments' do + inspect_source('User.destroy_all([])') + + expect(cop.offenses.size).to eq(1) + end + + it 'flags the use of destroy_all with a local variable receiver' do + inspect_source(<<~RUBY) + users = User.all + users.destroy_all + RUBY + + expect(cop.offenses.size).to eq(1) + end + + it 'does not flag the use of delete_all' do + inspect_source('foo.delete_all') + + expect(cop.offenses).to be_empty + end +end -- cgit v1.2.1 From 9606dbbb033567de9831a8cdea0e56236e7d2eb2 Mon Sep 17 00:00:00 2001 From: Yorick Peterse <yorickpeterse@gmail.com> Date: Thu, 16 Aug 2018 14:46:40 +0200 Subject: Whitelist existing destroy_all offenses This whitelists all existing places where we use "destroy_all". --- app/controllers/projects/pages_controller.rb | 2 +- app/models/concerns/awardable.rb | 2 +- app/models/concerns/fast_destroy_all.rb | 2 +- app/models/lfs_object.rb | 2 ++ app/models/user.rb | 2 +- app/services/labels/promote_service.rb | 2 +- app/services/milestones/promote_service.rb | 2 +- app/services/projects/move_deploy_keys_projects_service.rb | 2 +- app/services/projects/move_lfs_objects_projects_service.rb | 2 +- app/services/projects/move_notification_settings_service.rb | 2 +- app/services/projects/move_project_group_links_service.rb | 2 +- app/services/projects/move_project_members_service.rb | 2 +- app/services/protected_branches/legacy_api_update_service.rb | 4 ++-- app/workers/remove_expired_group_links_worker.rb | 2 +- app/workers/remove_old_web_hook_logs_worker.rb | 2 ++ db/migrate/20160712171823_remove_award_emojis_with_no_user.rb | 2 +- lib/gitlab/import_export/members_mapper.rb | 2 +- spec/controllers/omniauth_callbacks_controller_spec.rb | 2 +- spec/controllers/projects/releases_controller_spec.rb | 2 +- .../create_gpg_key_subkeys_from_gpg_keys_spec.rb | 2 +- .../schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb | 2 +- spec/models/fork_network_member_spec.rb | 2 +- spec/models/hooks/system_hook_spec.rb | 4 ++-- spec/models/merge_request_spec.rb | 2 +- spec/models/project_group_link_spec.rb | 2 +- spec/policies/group_policy_spec.rb | 2 +- spec/rubocop/cop/destroy_all_spec.rb | 6 +++--- spec/services/merge_requests/create_service_spec.rb | 2 ++ .../services/merge_requests/delete_non_latest_diffs_service_spec.rb | 2 ++ spec/services/todo_service_spec.rb | 2 +- spec/support/shared_examples/fast_destroy_all.rb | 4 ++-- spec/workers/repository_check/single_repository_worker_spec.rb | 2 +- 32 files changed, 41 insertions(+), 33 deletions(-) diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index cae6e2c40b8..ff49911d892 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -11,7 +11,7 @@ class Projects::PagesController < Projects::ApplicationController def destroy project.remove_pages - project.pages_domains.destroy_all + project.pages_domains.destroy_all # rubocop: disable DestroyAll respond_to do |format| format.html do diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index dd07f389fa5..49981db0d80 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -101,7 +101,7 @@ module Awardable end def remove_award_emoji(name, current_user) - award_emoji.where(name: name, user: current_user).destroy_all + award_emoji.where(name: name, user: current_user).destroy_all # rubocop: disable DestroyAll end def toggle_award_emoji(emoji_name, current_user) diff --git a/app/models/concerns/fast_destroy_all.rb b/app/models/concerns/fast_destroy_all.rb index 65ed46ea202..c342d01243e 100644 --- a/app/models/concerns/fast_destroy_all.rb +++ b/app/models/concerns/fast_destroy_all.rb @@ -34,7 +34,7 @@ module FastDestroyAll included do before_destroy do - raise ForbiddenActionError, '`destroy` and `destroy_all` are forbbiden. Please use `fast_destroy_all`' + raise ForbiddenActionError, '`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`' end end diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 2a1a4ef48b7..97bf5d611c2 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -29,11 +29,13 @@ class LfsObject < ActiveRecord::Base [nil, LfsObjectUploader::Store::LOCAL].include?(self.file_store) end + # rubocop: disable DestroyAll def self.destroy_unreferenced joins("LEFT JOIN lfs_objects_projects ON lfs_objects_projects.lfs_object_id = #{table_name}.id") .where(lfs_objects_projects: { id: nil }) .destroy_all end + # rubocop: enable DestroyAll def self.calculate_oid(path) Digest::SHA256.file(path).hexdigest diff --git a/app/models/user.rb b/app/models/user.rb index fb19de4b980..13b04270a4a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -516,7 +516,7 @@ class User < ActiveRecord::Base otp_grace_period_started_at: nil, otp_backup_codes: nil ) - self.u2f_registrations.destroy_all + self.u2f_registrations.destroy_all # rubocop: disable DestroyAll end end diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb index c0463052821..623a5f0950e 100644 --- a/app/services/labels/promote_service.rb +++ b/app/services/labels/promote_service.rb @@ -65,7 +65,7 @@ module Labels end def update_project_labels(label_ids) - Label.where(id: label_ids).destroy_all + Label.where(id: label_ids).destroy_all # rubocop: disable DestroyAll end def clone_label_to_group_label(label) diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb index 37aa6d3a9bc..660b4faaec0 100644 --- a/app/services/milestones/promote_service.rb +++ b/app/services/milestones/promote_service.rb @@ -73,7 +73,7 @@ module Milestones end def destroy_old_milestones(milestone) - Milestone.where(id: milestone_ids_for_merge(milestone)).destroy_all + Milestone.where(id: milestone_ids_for_merge(milestone)).destroy_all # rubocop: disable DestroyAll end def group_project_ids diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb index 40a22837eaf..9f3f44f30ea 100644 --- a/app/services/projects/move_deploy_keys_projects_service.rb +++ b/app/services/projects/move_deploy_keys_projects_service.rb @@ -27,7 +27,7 @@ module Projects end def remove_remaining_deploy_keys_projects - source_project.deploy_keys_projects.destroy_all + source_project.deploy_keys_projects.destroy_all # rubocop: disable DestroyAll end end end diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb index a5099519594..f78546a1e9c 100644 --- a/app/services/projects/move_lfs_objects_projects_service.rb +++ b/app/services/projects/move_lfs_objects_projects_service.rb @@ -21,7 +21,7 @@ module Projects end def remove_remaining_lfs_objects_project - source_project.lfs_objects_projects.destroy_all + source_project.lfs_objects_projects.destroy_all # rubocop: disable DestroyAll end def non_existent_lfs_objects_projects diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb index 746605d56f1..109a00dd6d9 100644 --- a/app/services/projects/move_notification_settings_service.rb +++ b/app/services/projects/move_notification_settings_service.rb @@ -22,7 +22,7 @@ module Projects # Remove remaining notification settings from source_project def remove_remaining_notification_settings - source_project.notification_settings.destroy_all + source_project.notification_settings.destroy_all # rubocop: disable DestroyAll end # Get users of current notification_settings diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb index d9038030f7e..1efafdce36d 100644 --- a/app/services/projects/move_project_group_links_service.rb +++ b/app/services/projects/move_project_group_links_service.rb @@ -26,7 +26,7 @@ module Projects # Remove remaining project group links from source_project def remove_remaining_project_group_links - source_project.reload.project_group_links.destroy_all + source_project.reload.project_group_links.destroy_all # rubocop: disable DestroyAll end def group_links_in_target_project diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb index bb0c0d10242..ec983582d94 100644 --- a/app/services/projects/move_project_members_service.rb +++ b/app/services/projects/move_project_members_service.rb @@ -25,7 +25,7 @@ module Projects def remove_remaining_members # Remove remaining members and authorizations from source_project - source_project.project_members.destroy_all + source_project.project_members.destroy_all # rubocop: disable DestroyAll end def project_members_in_target_project diff --git a/app/services/protected_branches/legacy_api_update_service.rb b/app/services/protected_branches/legacy_api_update_service.rb index 1f6bbe72f85..da8bf2ce02a 100644 --- a/app/services/protected_branches/legacy_api_update_service.rb +++ b/app/services/protected_branches/legacy_api_update_service.rb @@ -38,11 +38,11 @@ module ProtectedBranches def delete_redundant_access_levels unless @developers_can_merge.nil? - @protected_branch.merge_access_levels.destroy_all + @protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll end unless @developers_can_push.nil? - @protected_branch.push_access_levels.destroy_all + @protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll end end end diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb index 6b8b972a440..25128caf72f 100644 --- a/app/workers/remove_expired_group_links_worker.rb +++ b/app/workers/remove_expired_group_links_worker.rb @@ -5,6 +5,6 @@ class RemoveExpiredGroupLinksWorker include CronjobQueue def perform - ProjectGroupLink.expired.destroy_all + ProjectGroupLink.expired.destroy_all # rubocop: disable DestroyAll end end diff --git a/app/workers/remove_old_web_hook_logs_worker.rb b/app/workers/remove_old_web_hook_logs_worker.rb index 17140ac4450..0f486f8991d 100644 --- a/app/workers/remove_old_web_hook_logs_worker.rb +++ b/app/workers/remove_old_web_hook_logs_worker.rb @@ -6,7 +6,9 @@ class RemoveOldWebHookLogsWorker WEB_HOOK_LOG_LIFETIME = 2.days + # rubocop: disable DestroyAll def perform WebHookLog.destroy_all(['created_at < ?', Time.now - WEB_HOOK_LOG_LIFETIME]) end + # rubocop: enable DestroyAll end diff --git a/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb b/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb index 668c22bb51c..8ebf1a5234d 100644 --- a/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb +++ b/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb @@ -16,6 +16,6 @@ class RemoveAwardEmojisWithNoUser < ActiveRecord::Migration # disable_ddl_transaction! def up - AwardEmoji.joins('LEFT JOIN users ON users.id = user_id').where('users.id IS NULL').destroy_all + AwardEmoji.joins('LEFT JOIN users ON users.id = user_id').where('users.id IS NULL').destroy_all # rubocop: disable DestroyAll end end diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb index ac827cbe1ca..bcbaf00e11b 100644 --- a/lib/gitlab/import_export/members_mapper.rb +++ b/lib/gitlab/import_export/members_mapper.rb @@ -45,7 +45,7 @@ module Gitlab end def ensure_default_member! - @project.project_members.destroy_all + @project.project_members.destroy_all # rubocop: disable DestroyAll ProjectMember.create!(user: @user, access_level: ProjectMember::MAINTAINER, source_id: @project.id, importing: true) end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index b23f183fec8..d377d69457f 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -95,7 +95,7 @@ describe OmniauthCallbacksController, type: :controller do end it 'allows linking the disabled provider' do - user.identities.destroy_all + user.identities.destroy_all # rubocop: disable DestroyAll sign_in(user) expect { post provider }.to change { user.reload.identities.count }.by(1) diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index fc1619acec6..20a6beb3df8 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -14,7 +14,7 @@ describe Projects::ReleasesController do describe 'GET #edit' do it 'initializes a new release' do tag_id = release.tag - project.releases.destroy_all + project.releases.destroy_all # rubocop: disable DestroyAll get :edit, namespace_id: project.namespace, project_id: project, tag_id: tag_id diff --git a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb index 26d48cc8201..f92acf61682 100644 --- a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb +++ b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } before do - GpgKeySubkey.destroy_all + GpgKeySubkey.destroy_all # rubocop: disable DestroyAll end it 'generate the subkeys' do diff --git a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb index 96bef107599..c4427910518 100644 --- a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb +++ b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb @@ -6,7 +6,7 @@ describe ScheduleCreateGpgKeySubkeysFromGpgKeys, :migration, :sidekiq do create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs # Delete all subkeys so they can be recreated - GpgKeySubkey.destroy_all + GpgKeySubkey.destroy_all # rubocop: disable DestroyAll end it 'correctly schedules background migrations' do diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb index 25bf596fddc..60d04562e6c 100644 --- a/spec/models/fork_network_member_spec.rb +++ b/spec/models/fork_network_member_spec.rb @@ -11,7 +11,7 @@ describe ForkNetworkMember do let(:fork_network) { fork_network_member.fork_network } it 'removes the fork network if it was the last member' do - fork_network.fork_network_members.destroy_all + fork_network.fork_network_members.destroy_all # rubocop: disable DestroyAll expect(ForkNetwork.count).to eq(0) end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 01129df1107..edd1cb455af 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -73,7 +73,7 @@ describe SystemHook do it "project_destroy hook" do project.add_maintainer(user) - project.project_members.destroy_all + project.project_members.destroy_all # rubocop: disable DestroyAll expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_remove_from_team/, @@ -110,7 +110,7 @@ describe SystemHook do it 'group member destroy hook' do group.add_maintainer(user) - group.group_members.destroy_all + group.group_members.destroy_all # rubocop: disable DestroyAll expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_remove_from_group/, diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6258bfa232f..48f4e53b93e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1764,7 +1764,7 @@ describe MergeRequest do context 'with no discussions' do before do - merge_request.notes.destroy_all + merge_request.notes.destroy_all # rubocop: disable DestroyAll end it 'returns true' do diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 1fccf92627a..5bea21427d4 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -41,7 +41,7 @@ describe ProjectGroupLink do project.project_group_links.create(group: group) group_users.each { |user| expect(user.authorized_projects).to include(project) } - project.project_group_links.destroy_all + project.project_group_links.destroy_all # rubocop: disable DestroyAll group_users.each { |user| expect(user.authorized_projects).not_to include(project) } end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 35951251bc5..615fea11f26 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -205,7 +205,7 @@ describe GroupPolicy do nested_group.add_guest(developer) nested_group.add_guest(maintainer) - group.owners.destroy_all + group.owners.destroy_all # rubocop: disable DestroyAll group.add_guest(owner) nested_group.add_owner(owner) diff --git a/spec/rubocop/cop/destroy_all_spec.rb b/spec/rubocop/cop/destroy_all_spec.rb index 153a5a863cf..b0bc40552b3 100644 --- a/spec/rubocop/cop/destroy_all_spec.rb +++ b/spec/rubocop/cop/destroy_all_spec.rb @@ -9,13 +9,13 @@ describe RuboCop::Cop::DestroyAll do subject(:cop) { described_class.new } it 'flags the use of destroy_all with a send receiver' do - inspect_source('foo.destroy_all') + inspect_source('foo.destroy_all # rubocop: disable DestroyAll') expect(cop.offenses.size).to eq(1) end it 'flags the use of destroy_all with a constant receiver' do - inspect_source('User.destroy_all') + inspect_source('User.destroy_all # rubocop: disable DestroyAll') expect(cop.offenses.size).to eq(1) end @@ -29,7 +29,7 @@ describe RuboCop::Cop::DestroyAll do it 'flags the use of destroy_all with a local variable receiver' do inspect_source(<<~RUBY) users = User.all - users.destroy_all + users.destroy_all # rubocop: disable DestroyAll RUBY expect(cop.offenses.size).to eq(1) diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 06fb61baf33..74bcc15f912 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -134,9 +134,11 @@ describe MergeRequests::CreateService do let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) } before do + # rubocop: disable DestroyAll project.merge_requests .where(source_branch: opts[:source_branch], target_branch: opts[:target_branch]) .destroy_all + # rubocop: enable DestroyAll end it 'sets head pipeline' do diff --git a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb index 1c632847940..6268c149fc6 100644 --- a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb +++ b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb @@ -46,10 +46,12 @@ describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_ end it 'schedules no removal if there is no non-latest diffs' do + # rubocop: disable DestroyAll merge_request .merge_request_diffs .where.not(id: merge_request.latest_merge_request_diff_id) .destroy_all + # rubocop: enable DestroyAll expect(DeleteDiffFilesWorker).not_to receive(:bulk_perform_in) diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 9a51c873b30..1746721b0d0 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -280,7 +280,7 @@ describe TodoService do end it 'does not create a todo if unassigned' do - issue.assignees.destroy_all + issue.assignees.destroy_all # rubocop: disable DestroyAll should_not_create_any_todo { service.reassigned_issue(issue, author) } end diff --git a/spec/support/shared_examples/fast_destroy_all.rb b/spec/support/shared_examples/fast_destroy_all.rb index 5448ddcfe33..a8079b6d864 100644 --- a/spec/support/shared_examples/fast_destroy_all.rb +++ b/spec/support/shared_examples/fast_destroy_all.rb @@ -4,8 +4,8 @@ shared_examples_for 'fast destroyable' do expect(external_data_counter).to be > 0 expect(subjects.count).to be > 0 - expect { subjects.first.destroy }.to raise_error('`destroy` and `destroy_all` are forbbiden. Please use `fast_destroy_all`') - expect { subjects.destroy_all }.to raise_error('`destroy` and `destroy_all` are forbbiden. Please use `fast_destroy_all`') + expect { subjects.first.destroy }.to raise_error('`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`') + expect { subjects.destroy_all }.to raise_error('`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`') # rubocop: disable DestroyAll expect(subjects.count).to be > 0 expect(external_data_counter).to be > 0 diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index 22fc64c1536..f11875cffd1 100644 --- a/spec/workers/repository_check/single_repository_worker_spec.rb +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -6,7 +6,7 @@ describe RepositoryCheck::SingleRepositoryWorker do it 'skips when the project has no push events' do project = create(:project, :repository, :wiki_disabled) - project.events.destroy_all + project.events.destroy_all # rubocop: disable DestroyAll break_project(project) expect(worker).not_to receive(:git_fsck) -- cgit v1.2.1 From 0ac3cd801d0370ea62e6ec998eb2ada02a939f4b Mon Sep 17 00:00:00 2001 From: Phil Hughes <me@iamphill.com> Date: Thu, 16 Aug 2018 16:42:37 +0100 Subject: fixed karma spec --- spec/javascripts/ide/stores/actions/file_spec.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index 72eb20bdc87..bca2033ff97 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -352,10 +352,22 @@ describe('IDE store file actions', () => { it('calls also getBaseRawFileData service method', done => { spyOn(service, 'getBaseRawFileData').and.returnValue(Promise.resolve('baseraw')); + store.state.currentProjectId = 'gitlab-org/gitlab-ce'; + store.state.currentMergeRequestId = '1'; + store.state.projects = { + 'gitlab-org/gitlab-ce': { + mergeRequests: { + 1: { + baseCommitSha: 'SHA', + }, + }, + }, + }; + tmpFile.mrChange = { new_file: false }; store - .dispatch('getRawFileData', { path: tmpFile.path, baseSha: 'SHA' }) + .dispatch('getRawFileData', { path: tmpFile.path }) .then(() => { expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA'); expect(tmpFile.baseRaw).toBe('baseraw'); @@ -392,10 +404,7 @@ describe('IDE store file actions', () => { const dispatch = jasmine.createSpy('dispatch'); actions - .getRawFileData( - { state: store.state, commit() {}, dispatch }, - { path: tmpFile.path, baseSha: tmpFile.baseSha }, - ) + .getRawFileData({ state: store.state, commit() {}, dispatch }, { path: tmpFile.path }) .then(done.fail) .catch(() => { expect(dispatch).toHaveBeenCalledWith('setErrorMessage', { @@ -404,7 +413,6 @@ describe('IDE store file actions', () => { actionText: 'Please try again', actionPayload: { path: tmpFile.path, - baseSha: tmpFile.baseSha, }, }); -- cgit v1.2.1 From 935bc24eb4212a3da14332ad1f613d4974e6f6ee Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis <axil@gitlab.com> Date: Thu, 16 Aug 2018 15:49:57 +0000 Subject: Improve project manifest import docs --- doc/user/project/import/img/manifest_upload.png | Bin 12079 -> 0 bytes doc/user/project/import/index.md | 2 +- doc/user/project/import/manifest.md | 63 +++++++++++++----------- 3 files changed, 36 insertions(+), 29 deletions(-) delete mode 100644 doc/user/project/import/img/manifest_upload.png diff --git a/doc/user/project/import/img/manifest_upload.png b/doc/user/project/import/img/manifest_upload.png deleted file mode 100644 index d6bf4b157dd..00000000000 Binary files a/doc/user/project/import/img/manifest_upload.png and /dev/null differ diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index b55435e5b4f..4ea35a30bbf 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -11,7 +11,7 @@ 1. [From SVN](svn.md) 1. [From TFS](tfs.md) 1. [From repo by URL](repo_by_url.md) -1. [By uploading a manifest file](manifest.md) +1. [By uploading a manifest file (AOSP)](manifest.md) In addition to the specific migration documentation above, you can import any Git repository via HTTP from the New Project page. Be aware that if the diff --git a/doc/user/project/import/manifest.md b/doc/user/project/import/manifest.md index 06171f11e12..296e30aa0c3 100644 --- a/doc/user/project/import/manifest.md +++ b/doc/user/project/import/manifest.md @@ -1,36 +1,28 @@ # Import multiple repositories by uploading a manifest file -GitLab allows you to import all the required git repositories -based a manifest file like the one used by the [Android repository](https://android.googlesource.com/platform/manifest/+/2d6f081a3b05d8ef7a2b1b52b0d536b2b74feab4/default.xml). -This feature can be very handy when you need to import a project with many repositories like Android Open Source Project (AOSP). +GitLab allows you to import all the required Git repositories +based on a manifest file like the one used by the +[Android repository](https://android.googlesource.com/platform/manifest/+/2d6f081a3b05d8ef7a2b1b52b0d536b2b74feab4/default.xml). +This feature can be very handy when you need to import a project with many +repositories like the Android Open Source Project (AOSP). +## Requirements ->**Note:** -This feature requires [subgroups](../../group/subgroups/index.md) to be supported by your database. +GitLab must be using PostgreSQL for its database, since +[subgroups](../../group/subgroups/index.md) are needed for the manifest import +to work. -You can do it by following next steps: +Read more about the [database requirements](../../../install/requirements.md#database). -1. From your GitLab dashboard click **New project** -1. Switch to the **Import project** tab -1. Click on the **Manifest file** button -1. Provide GitLab with a manifest xml file -1. Select a group you want to import to (you need to create a group first if you don't have one) -1. Click **List available repositories** -1. You will be redirected to the import status page with projects list based on manifest file -1. Check the list and click 'Import all repositories' to start import. +## Manifest format -![Manifest upload](img/manifest_upload.png) +A manifest must be an XML file. There must be one `remote` tag with a `review` +attribute that contains a URL to a Git server, and each `project` tag must have +a `name` and `path` attribute. GitLab will then build the URL to the repository +by combining the URL from the `remote` tag with a project name. +A path attribute will be used to represent the project path in GitLab. -![Manifest status](img/manifest_status.png) - -### Manifest format - -A manifest must be an XML file. There must be one `remote` tag with `review` attribute -that contains a URL to a git server. Each `project` tag must have `name` and `path` attribute. -GitLab will build URL to the repository by combining URL from `remote` tag with a project name. -A path attribute will be used to represent project path in GitLab system. - -Below is a valid example of manifest file. +Below is a valid example of a manifest file: ```xml <manifest> @@ -41,9 +33,24 @@ Below is a valid example of manifest file. </manifest> ``` -As result next projects will be created: +As a result, the following projects will be created: | GitLab | Import URL | |---|---| -| https://gitlab/YOUR_GROUP/build/make | https://android-review.googlesource.com/platform/build | -| https://gitlab/YOUR_GROUP/build/blueprint | https://android-review.googlesource.com/platform/build/blueprint | +| https://gitlab.com/YOUR_GROUP/build/make | https://android-review.googlesource.com/platform/build | +| https://gitlab.com/YOUR_GROUP/build/blueprint | https://android-review.googlesource.com/platform/build/blueprint | + +## Importing the repositories + +You can start the import with: + +1. From your GitLab dashboard click **New project** +1. Switch to the **Import project** tab +1. Click on the **Manifest file** button +1. Provide GitLab with a manifest xml file +1. Select a group you want to import to (you need to create a group first if you don't have one) +1. Click **List available repositories**. At this point, you will be redirected + to the import status page with projects list based on the manifest file. +1. Check the list and click **Import all repositories** to start the import. + + ![Manifest status](img/manifest_status.png) -- cgit v1.2.1 From a0b92642e3068e65f51b3b3d4daba14d9e0aa8d4 Mon Sep 17 00:00:00 2001 From: Jasper Maes <jaspermaes.jm@gmail.com> Date: Thu, 16 Aug 2018 17:55:29 +0200 Subject: [ci skip] Change changelog type to other --- changelogs/unreleased/rails5-verbose-query-logs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/rails5-verbose-query-logs.yml b/changelogs/unreleased/rails5-verbose-query-logs.yml index c6d0ca9c841..7585e75d30b 100644 --- a/changelogs/unreleased/rails5-verbose-query-logs.yml +++ b/changelogs/unreleased/rails5-verbose-query-logs.yml @@ -2,4 +2,4 @@ title: 'Rails5: Enable verbose query logs' merge_request: 21231 author: Jasper Maes -type: fixed +type: other -- cgit v1.2.1 From 1e2918eb107f8c94cd0f53e3d3eb9e6102d92700 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis <axil@gitlab.com> Date: Thu, 16 Aug 2018 17:56:00 +0200 Subject: Add when manifest feature was introduced --- doc/user/project/import/manifest.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/user/project/import/manifest.md b/doc/user/project/import/manifest.md index 296e30aa0c3..24bf6541a9d 100644 --- a/doc/user/project/import/manifest.md +++ b/doc/user/project/import/manifest.md @@ -1,5 +1,8 @@ # Import multiple repositories by uploading a manifest file +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28811) in +GitLab 11.2. + GitLab allows you to import all the required Git repositories based on a manifest file like the one used by the [Android repository](https://android.googlesource.com/platform/manifest/+/2d6f081a3b05d8ef7a2b1b52b0d536b2b74feab4/default.xml). -- cgit v1.2.1 From 5e1cd6b7e3719cdf8cf7adee9f1898eaaaa720c1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis <axil@gitlab.com> Date: Thu, 16 Aug 2018 18:06:20 +0200 Subject: Add when the hangouts integration was introduced --- doc/user/project/integrations/hangouts_chat.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md index 6ab44420a10..47525617d95 100644 --- a/doc/user/project/integrations/hangouts_chat.md +++ b/doc/user/project/integrations/hangouts_chat.md @@ -1,5 +1,7 @@ # Hangouts Chat service +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/43756) in GitLab 11.2. + The Hangouts Chat service sends notifications from GitLab to the room for which the webhook was created. ## On Hangouts Chat -- cgit v1.2.1 From fd9377fa1a4e1eaef280200430c47036d0e58d71 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto <brodock@gmail.com> Date: Sat, 4 Aug 2018 01:02:38 +0200 Subject: Added method to re-populating SiteStatitiscs counter --- app/models/site_statistic.rb | 16 +++++++++++++++- spec/models/site_statistic_spec.rb | 12 ++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb index daac1c57db9..2530a9d8b8e 100644 --- a/app/models/site_statistic.rb +++ b/app/models/site_statistic.rb @@ -49,7 +49,7 @@ class SiteStatistic < ActiveRecord::Base # # @return [SiteStatistic] record with tracked information def self.fetch - SiteStatistic.transaction(requires_new: true) do + transaction(requires_new: true) do SiteStatistic.first_or_create! end rescue ActiveRecord::RecordNotUnique @@ -73,4 +73,18 @@ class SiteStatistic < ActiveRecord::Base super end + + def self.recalculate_counters! + transaction do + # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967 + ActiveRecord::Base.connection.execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql? + self.update_all('repositories_count = (SELECT COUNT(*) FROM projects)') + end + + transaction do + # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967 + ActiveRecord::Base.connection.execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql? + self.update_all('wikis_count = (SELECT COUNT(*) FROM project_features WHERE wiki_access_level != 0)') + end + end end diff --git a/spec/models/site_statistic_spec.rb b/spec/models/site_statistic_spec.rb index 9b056fbf332..49b54fb6994 100644 --- a/spec/models/site_statistic_spec.rb +++ b/spec/models/site_statistic_spec.rb @@ -80,4 +80,16 @@ describe SiteStatistic do end end end + + describe '.recalculate_counters!' do + it 'recalculates existing counters' do + create(:project) + described_class.fetch.update(repositories_count: 0, wikis_count: 0) + + described_class.recalculate_counters! + + expect(described_class.fetch.repositories_count).to eq(1) + expect(described_class.fetch.wikis_count).to eq(1) + end + end end -- cgit v1.2.1 From 85e83813379bb7be86e266c67dedcfee2cd6d738 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto <brodock@gmail.com> Date: Thu, 9 Aug 2018 20:41:39 +0200 Subject: Add WikiAccessLevel migration from `NULL` to `20` --- ...180809195358_migrate_null_wiki_access_levels.rb | 23 +++++++++++++++++ db/schema.rb | 2 +- .../migrate_null_wiki_access_levels_spec.rb | 29 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb create mode 100644 spec/migrations/migrate_null_wiki_access_levels_spec.rb diff --git a/db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb b/db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb new file mode 100644 index 00000000000..d3557efecbd --- /dev/null +++ b/db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class MigrateNullWikiAccessLevels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + class ProjectFeature < ActiveRecord::Base + include EachBatch + + self.table_name = 'project_features' + end + + def up + ProjectFeature.where(wiki_access_level: nil).each_batch do |relation| + relation.update_all(wiki_access_level: 20) + end + end + + def down + # there is no way to rollback this change, there are no downsides in keeping migrated data. + end +end diff --git a/db/schema.rb b/db/schema.rb index 1288a98745c..9dc122b54b3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180808162000) do +ActiveRecord::Schema.define(version: 20180809195358) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/spec/migrations/migrate_null_wiki_access_levels_spec.rb b/spec/migrations/migrate_null_wiki_access_levels_spec.rb new file mode 100644 index 00000000000..f99273072a2 --- /dev/null +++ b/spec/migrations/migrate_null_wiki_access_levels_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180809195358_migrate_null_wiki_access_levels.rb') + +describe MigrateNullWikiAccessLevels, :migration do + let(:namespaces) { table('namespaces') } + let(:projects) { table(:projects) } + let(:project_features) { table(:project_features) } + let(:migration) { described_class.new } + + before do + namespace = namespaces.create(name: 'foo', path: 'foo') + + projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1', namespace_id: namespace.id) + projects.create!(id: 2, name: 'gitlab2', path: 'gitlab2', namespace_id: namespace.id) + projects.create!(id: 3, name: 'gitlab3', path: 'gitlab3', namespace_id: namespace.id) + + project_features.create!(id: 1, project_id: 1, wiki_access_level: nil) + project_features.create!(id: 2, project_id: 2, wiki_access_level: 10) + project_features.create!(id: 3, project_id: 3, wiki_access_level: 20) + end + + describe '#up' do + it 'migrates existing project_features with wiki_access_level NULL to 20' do + expect { migration.up }.to change { project_features.where(wiki_access_level: 20).count }.by(1) + end + end +end -- cgit v1.2.1 From 010fbedb61334a303d26164de905d9d421871ff2 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto <brodock@gmail.com> Date: Thu, 9 Aug 2018 21:13:14 +0200 Subject: Changelog --- changelogs/unreleased/repopulate_site_statistics.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/repopulate_site_statistics.yml diff --git a/changelogs/unreleased/repopulate_site_statistics.yml b/changelogs/unreleased/repopulate_site_statistics.yml new file mode 100644 index 00000000000..1961088061d --- /dev/null +++ b/changelogs/unreleased/repopulate_site_statistics.yml @@ -0,0 +1,5 @@ +--- +title: Migrate NULL wiki_access_level to correct number so we count active wikis correctly +merge_request: 21030 +author: +type: changed -- cgit v1.2.1 From 8f9442fcdf9d98ea28e6abd582aad1dc2086e1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8C=B4=F0=9F=87=BF=F0=9F=87=A6=20Lukas=20Eipert=20?= =?UTF-8?q?=28OOO=20until=20Summit=29?= <leipert@gitlab.com> Date: Thu, 16 Aug 2018 19:03:57 +0000 Subject: Frontend: Proper gettext extraction with gettext-extractor --- app/assets/javascripts/importer_status.js | 2 +- .../javascripts/locale/ensure_single_line.js | 25 +++ app/assets/javascripts/locale/index.js | 13 +- .../sidebar/components/lock/edit_form_buttons.vue | 3 +- .../sidebar/components/lock/lock_issue_sidebar.vue | 10 +- config/initializers/gettext_rails_i18n_patch.rb | 15 ++ locale/gitlab.pot | 185 +++++++++++++++++- package.json | 2 + scripts/frontend/extract_gettext_all.js | 72 +++++++ spec/javascripts/locale/ensure_single_line_spec.js | 35 ++++ spec/javascripts/notes/mock_data.js | 2 +- spec/javascripts/vue_shared/translate_spec.js | 213 ++++++++++++++++++--- yarn.lock | 80 +++++++- 13 files changed, 606 insertions(+), 51 deletions(-) create mode 100644 app/assets/javascripts/locale/ensure_single_line.js create mode 100644 scripts/frontend/extract_gettext_all.js create mode 100644 spec/javascripts/locale/ensure_single_line_spec.js diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 0035d809062..eda8cdad908 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -87,7 +87,7 @@ class ImporterStatus { details = error.response.data.errors; } - flash(__(`An error occurred while importing project: ${details}`)); + flash(sprintf(__('An error occurred while importing project: %{details}'), { details })); }); } diff --git a/app/assets/javascripts/locale/ensure_single_line.js b/app/assets/javascripts/locale/ensure_single_line.js new file mode 100644 index 00000000000..47c52fe6c50 --- /dev/null +++ b/app/assets/javascripts/locale/ensure_single_line.js @@ -0,0 +1,25 @@ +/* eslint-disable import/no-commonjs */ + +const SPLIT_REGEX = /\s*[\r\n]+\s*/; + +/** + * + * strips newlines from strings and replaces them with a single space + * + * @example + * + * ensureSingleLine('foo \n bar') === 'foo bar' + * + * @param {String} str + * @returns {String} + */ +module.exports = function ensureSingleLine(str) { + // This guard makes the function significantly faster + if (str.includes('\n') || str.includes('\r')) { + return str + .split(SPLIT_REGEX) + .filter(s => s !== '') + .join(' '); + } + return str; +}; diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js index 2cc5fb10027..1ae3362c4bc 100644 --- a/app/assets/javascripts/locale/index.js +++ b/app/assets/javascripts/locale/index.js @@ -1,4 +1,5 @@ import Jed from 'jed'; +import ensureSingleLine from './ensure_single_line'; import sprintf from './sprintf'; const languageCode = () => document.querySelector('html').getAttribute('lang') || 'en'; @@ -10,7 +11,7 @@ delete window.translations; @param text The text to be translated @returns {String} The translated text */ -const gettext = locale.gettext.bind(locale); +const gettext = text => locale.gettext.bind(locale)(ensureSingleLine(text)); /** Translate the text with a number @@ -23,7 +24,10 @@ const gettext = locale.gettext.bind(locale); @returns {String} Translated text with the number replaced (eg. '2 days') */ const ngettext = (text, pluralText, count) => { - const translated = locale.ngettext(text, pluralText, count).replace(/%d/g, count).split('|'); + const translated = locale + .ngettext(ensureSingleLine(text), ensureSingleLine(pluralText), count) + .replace(/%d/g, count) + .split('|'); return translated[translated.length - 1]; }; @@ -40,7 +44,7 @@ const ngettext = (text, pluralText, count) => { @returns {String} Translated context based text */ const pgettext = (keyOrContext, key) => { - const normalizedKey = key ? `${keyOrContext}|${key}` : keyOrContext; + const normalizedKey = ensureSingleLine(key ? `${keyOrContext}|${key}` : keyOrContext); const translated = gettext(normalizedKey).split('|'); return translated[translated.length - 1]; @@ -52,8 +56,7 @@ const pgettext = (keyOrContext, key) => { @param formatOptions for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat @returns {Intl.DateTimeFormat} */ -const createDateTimeFormat = - formatOptions => Intl.DateTimeFormat(languageCode(), formatOptions); +const createDateTimeFormat = formatOptions => Intl.DateTimeFormat(languageCode(), formatOptions); export { languageCode }; export { gettext as __ }; diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue index 5e7b8f9698f..63082654101 100644 --- a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue +++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue @@ -1,4 +1,5 @@ <script> +import { __ } from '~/locale'; import $ from 'jquery'; import eventHub from '../../event_hub'; @@ -17,7 +18,7 @@ export default { computed: { buttonText() { - return this.isLocked ? this.__('Unlock') : this.__('Lock'); + return this.isLocked ? __('Unlock') : __('Lock'); }, toggleLock() { diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue index 8bbc59f623a..ab7fab7e5ca 100644 --- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue @@ -1,5 +1,5 @@ <script> -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; import Flash from '~/flash'; import tooltip from '~/vue_shared/directives/tooltip'; import issuableMixin from '~/vue_shared/mixins/issuable'; @@ -79,11 +79,9 @@ export default { .then(() => window.location.reload()) .catch(() => Flash( - this.__( - `Something went wrong trying to change the locked state of this ${ - this.issuableDisplayName - }`, - ), + sprintf(__('Something went wrong trying to change the locked state of this %{issuableDisplayName}'), { + issuableDisplayName: this.issuableDisplayName, + }), ), ); }, diff --git a/config/initializers/gettext_rails_i18n_patch.rb b/config/initializers/gettext_rails_i18n_patch.rb index 49551319435..c1342f48ebd 100644 --- a/config/initializers/gettext_rails_i18n_patch.rb +++ b/config/initializers/gettext_rails_i18n_patch.rb @@ -1,5 +1,6 @@ require 'gettext_i18n_rails/haml_parser' require 'gettext_i18n_rails_js/parser/javascript' +require 'json' VUE_TRANSLATE_REGEX = /((%[\w.-]+)(?:\s))?{{ (N|n|s)?__\((.*)\) }}/ @@ -36,6 +37,20 @@ module GettextI18nRailsJs ".vue" ].include? ::File.extname(file) end + + def collect_for(file) + gettext_messages_by_file[file] || [] + end + + private + + def gettext_messages_by_file + @gettext_messages_by_file ||= JSON.parse(load_messages) + end + + def load_messages + `node scripts/frontend/extract_gettext_all.js --all` + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b370cc13f11..a4dde120c57 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -235,6 +235,9 @@ msgstr "" msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address." msgstr "" +msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes" +msgstr "" + msgid "<strong>%{group_name}</strong> group members" msgstr "" @@ -346,6 +349,12 @@ msgstr "" msgid "Admin area" msgstr "" +msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered." +msgstr "" + +msgid "AdminArea| You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered." +msgstr "" + msgid "AdminArea|Stop all jobs" msgstr "" @@ -364,6 +373,9 @@ msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered." +msgstr "" + msgid "AdminProjects|Delete" msgstr "" @@ -499,7 +511,7 @@ msgstr "" msgid "An error occurred while getting projects" msgstr "" -msgid "An error occurred while importing project: ${details}" +msgid "An error occurred while importing project: %{details}" msgstr "" msgid "An error occurred while loading commit signatures" @@ -805,6 +817,9 @@ msgstr "" msgid "Badges|This project has no badges" msgstr "" +msgid "Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored." +msgstr "" + msgid "Badges|Your badges" msgstr "" @@ -1248,6 +1263,9 @@ msgstr "" msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" +msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}." +msgstr "" + msgid "ClusterIntegration|API URL" msgstr "" @@ -1257,12 +1275,21 @@ msgstr "" msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" +msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}" +msgstr "" + msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}" msgstr "" msgid "ClusterIntegration|An error occured while trying to fetch your projects: %{error}" msgstr "" +msgid "ClusterIntegration|An error occured while trying to fetch zone machine types: %{error}" +msgstr "" + +msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later." +msgstr "" + msgid "ClusterIntegration|Applications" msgstr "" @@ -1329,6 +1356,9 @@ msgstr "" msgid "ClusterIntegration|GitLab Runner" msgstr "" +msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production." +msgstr "" + msgid "ClusterIntegration|Google Cloud Platform project" msgstr "" @@ -1341,6 +1371,9 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" +msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts." +msgstr "" + msgid "ClusterIntegration|Hide" msgstr "" @@ -1350,9 +1383,15 @@ msgstr "" msgid "ClusterIntegration|Ingress IP Address" msgstr "" +msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint." +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" +msgid "ClusterIntegration|Install applications on your Kubernetes cluster. Read more about %{helpLink}" +msgstr "" + msgid "ClusterIntegration|Installed" msgstr "" @@ -1371,6 +1410,9 @@ msgstr "" msgid "ClusterIntegration|JupyterHub" msgstr "" +msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group." +msgstr "" + msgid "ClusterIntegration|Kubernetes cluster" msgstr "" @@ -1458,6 +1500,9 @@ msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" +msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed." +msgstr "" + msgid "ClusterIntegration|Project namespace" msgstr "" @@ -1467,6 +1512,9 @@ msgstr "" msgid "ClusterIntegration|Prometheus" msgstr "" +msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications." +msgstr "" + msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." msgstr "" @@ -1479,6 +1527,9 @@ msgstr "" msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" +msgid "ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above." +msgstr "" + msgid "ClusterIntegration|Request to begin installing failed" msgstr "" @@ -1533,6 +1584,9 @@ msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" +msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time." +msgstr "" + msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application." msgstr "" @@ -1551,6 +1605,9 @@ msgstr "" msgid "ClusterIntegration|Validating project billing status" msgstr "" +msgid "ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" @@ -1963,6 +2020,9 @@ msgstr "" msgid "Cycle Analytics" msgstr "" +msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." +msgstr "" + msgid "CycleAnalyticsStage|Code" msgstr "" @@ -2361,6 +2421,9 @@ msgstr "" msgid "Environments|Environments" msgstr "" +msgid "Environments|Environments are places where code gets deployed, such as staging or production." +msgstr "" + msgid "Environments|Job" msgstr "" @@ -2373,6 +2436,9 @@ msgstr "" msgid "Environments|No deployments yet" msgstr "" +msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file." +msgstr "" + msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file." msgstr "" @@ -3181,6 +3247,9 @@ msgstr "" msgid "Job|The artifacts will be removed" msgstr "" +msgid "Job|This job is stuck, because the project doesn't have any runners online assigned to it." +msgstr "" + msgid "Jul" msgstr "" @@ -3262,6 +3331,9 @@ msgstr "" msgid "Labels|Promote Label" msgstr "" +msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "" @@ -3321,6 +3393,11 @@ msgstr "" msgid "Leave the \"File type\" and \"Delivery method\" options on their default values." msgstr "" +msgid "Limited to showing %d event at most" +msgid_plural "Limited to showing %d events at most" +msgstr[0] "" +msgstr[1] "" + msgid "LinkedIn" msgstr "" @@ -3357,6 +3434,9 @@ msgstr "" msgid "Lock not found" msgstr "" +msgid "Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment." +msgstr "" + msgid "Lock to current projects" msgstr "" @@ -3483,6 +3563,9 @@ msgstr "" msgid "MergeRequests|View replaced file @ %{commitId}" msgstr "" +msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" +msgstr "" + msgid "Merged" msgstr "" @@ -3531,6 +3614,12 @@ msgstr "" msgid "Milestones" msgstr "" +msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} from this project and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. Once deleted, it cannot be undone or recovered." +msgstr "" + +msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle} from this project. %{milestoneTitle} is not currently used in any issues or merge requests." +msgstr "" + msgid "Milestones|Delete milestone" msgstr "" @@ -3549,6 +3638,9 @@ msgstr "" msgid "Milestones|Promote Milestone" msgstr "" +msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged. This action cannot be reversed." +msgstr "" + msgid "Mirror a repository" msgstr "" @@ -3719,6 +3811,9 @@ msgstr "" msgid "No connection could be made to a Gitaly Server, please check your logs!" msgstr "" +msgid "No container images stored for this project. Add one by following the instructions above." +msgstr "" + msgid "No due date" msgstr "" @@ -3764,6 +3859,12 @@ msgstr "" msgid "None" msgstr "" +msgid "Not all comments are displayed because you're comparing two versions of the diff." +msgstr "" + +msgid "Not all comments are displayed because you're viewing an old version of the diff." +msgstr "" + msgid "Not allowed to merge" msgstr "" @@ -3875,6 +3976,11 @@ msgstr "" msgid "OfSearchInADropdown|Filter" msgstr "" +msgid "One more item" +msgid_plural "%d more items" +msgstr[0] "" +msgstr[1] "" + msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git." msgstr "" @@ -3983,6 +4089,9 @@ msgstr "" msgid "Pending" msgstr "" +msgid "People without permission will never get a notification and won't be able to comment." +msgstr "" + msgid "Per job. If a job passes this threshold, it will be marked as failed" msgstr "" @@ -4085,6 +4194,9 @@ msgstr "" msgid "Pipelines|Clear Runner Caches" msgstr "" +msgid "Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment." +msgstr "" + msgid "Pipelines|Get started with Pipelines" msgstr "" @@ -4106,6 +4218,9 @@ msgstr "" msgid "Pipelines|There are currently no pipelines." msgstr "" +msgid "Pipelines|There was an error fetching the pipelines. Try again in a few moments or contact your support team." +msgstr "" + msgid "Pipelines|This project is not currently set up to run pipelines." msgstr "" @@ -4220,6 +4335,12 @@ msgstr "" msgid "Profile Settings" msgstr "" +msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered." +msgstr "" + +msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible." +msgstr "" + msgid "Profiles|Account scheduled for removal." msgstr "" @@ -4394,6 +4515,9 @@ msgstr "" msgid "ProjectsDropdown|Sorry, no projects matched your search" msgstr "" +msgid "ProjectsDropdown|This feature requires browser localStorage support" +msgstr "" + msgid "PrometheusDashboard|Time" msgstr "" @@ -4520,6 +4644,11 @@ msgstr "" msgid "Reference:" msgstr "" +msgid "Refreshing in a second to show the updated status..." +msgid_plural "Refreshing in %d seconds to show the updated status..." +msgstr[0] "" +msgstr[1] "" + msgid "Register / Sign In" msgstr "" @@ -4983,6 +5112,12 @@ msgstr "" msgid "Something went wrong on our end. Please try again!" msgstr "" +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + +msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}" +msgstr "" + msgid "Something went wrong when toggling the button" msgstr "" @@ -5321,6 +5456,9 @@ msgstr "" msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" +msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git." +msgstr "" + msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgstr "" @@ -5351,6 +5489,9 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "" +msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." +msgstr "" + msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "" @@ -5453,6 +5594,9 @@ msgstr "" msgid "This application will be able to:" msgstr "" +msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgstr "" + msgid "This diff is collapsed." msgstr "" @@ -5504,6 +5648,12 @@ msgstr "" msgid "This job is in pending state and is waiting to be picked by a runner" msgstr "" +msgid "This job is stuck, because you don't have any active runners online with any of these tags assigned to them:" +msgstr "" + +msgid "This job is stuck, because you don't have any active runners that can run this job." +msgstr "" + msgid "This job requires a manual action" msgstr "" @@ -5513,6 +5663,9 @@ msgstr "" msgid "This merge request is locked." msgstr "" +msgid "This option is disabled as you don't have write permissions for the current branch" +msgstr "" + msgid "This option is disabled while you still have unstaged changes" msgstr "" @@ -5528,6 +5681,9 @@ msgstr "" msgid "This project does not belong to a group and can therefore not make use of group Runners." msgstr "" +msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again." +msgstr "" + msgid "This repository" msgstr "" @@ -5820,6 +5976,9 @@ msgstr "" msgid "Unlock" msgstr "" +msgid "Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment." +msgstr "" + msgid "Unlocked" msgstr "" @@ -6333,6 +6492,12 @@ msgstr "" msgid "command line instructions" msgstr "" +msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue." +msgstr "" + msgid "connecting" msgstr "" @@ -6436,6 +6601,9 @@ msgstr "" msgid "mrWidget|Failed to load deployment statistics" msgstr "" +msgid "mrWidget|Fast-forward merge is not possible. To merge this request, first rebase locally." +msgstr "" + msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" @@ -6463,9 +6631,15 @@ msgstr "" msgid "mrWidget|Open in Web IDE" msgstr "" +msgid "mrWidget|Pipeline blocked. The pipeline for this merge request requires a manual action to proceed" +msgstr "" + msgid "mrWidget|Plain diff" msgstr "" +msgid "mrWidget|Ready to be merged automatically. Ask someone with write access to this repository to merge this request" +msgstr "" + msgid "mrWidget|Refresh" msgstr "" @@ -6487,6 +6661,9 @@ msgstr "" msgid "mrWidget|Resolve conflicts" msgstr "" +msgid "mrWidget|Resolve these conflicts or ask someone with write access to this repository to merge it locally" +msgstr "" + msgid "mrWidget|Revert" msgstr "" @@ -6505,6 +6682,12 @@ msgstr "" msgid "mrWidget|The changes will be merged into" msgstr "" +msgid "mrWidget|The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure" +msgstr "" + +msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging" +msgstr "" + msgid "mrWidget|The source branch has been removed" msgstr "" diff --git a/package.json b/package.json index 975dd2619d7..f5fc759f76e 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,8 @@ "eslint-plugin-jasmine": "^2.1.0", "eslint-plugin-promise": "^3.8.0", "eslint-plugin-vue": "^4.5.0", + "gettext-extractor": "^3.3.2", + "gettext-extractor-vue": "^4.0.1", "ignore": "^3.3.7", "istanbul": "^0.4.5", "jasmine-core": "^2.9.0", diff --git a/scripts/frontend/extract_gettext_all.js b/scripts/frontend/extract_gettext_all.js new file mode 100644 index 00000000000..af8cc4c3341 --- /dev/null +++ b/scripts/frontend/extract_gettext_all.js @@ -0,0 +1,72 @@ +const argumentsParser = require('commander'); + +const { GettextExtractor, JsExtractors } = require('gettext-extractor'); +const { + decorateJSParserWithVueSupport, + decorateExtractorWithHelpers, +} = require('gettext-extractor-vue'); +const ensureSingleLine = require('../../app/assets/javascripts/locale/ensure_single_line.js'); + +const arguments = argumentsParser + .option('-f, --file <file>', 'Extract message from one single file') + .option('-a, --all', 'Extract message from all js/vue files') + .parse(process.argv); + +const extractor = decorateExtractorWithHelpers(new GettextExtractor()); + +extractor.addMessageTransformFunction(ensureSingleLine); + +const jsParser = extractor.createJsParser([ + // Place all the possible expressions to extract here: + JsExtractors.callExpression('__', { + arguments: { + text: 0, + }, + }), + JsExtractors.callExpression('n__', { + arguments: { + text: 0, + textPlural: 1, + }, + }), + JsExtractors.callExpression('s__', { + arguments: { + text: 0, + }, + }), +]); + +const vueParser = decorateJSParserWithVueSupport(jsParser); + +function printJson() { + const messages = extractor.getMessages().reduce((result, message) => { + let text = message.text; + if (message.textPlural) { + text += `\u0000${message.textPlural}`; + } + + message.references.forEach(reference => { + const filename = reference.replace(/:\d+$/, ''); + + if (!Array.isArray(result[filename])) { + result[filename] = []; + } + + result[filename].push([text, reference]); + }); + + return result; + }, {}); + + console.log(JSON.stringify(messages)); +} + +if (arguments.file) { + vueParser.parseFile(arguments.file).then(() => printJson()); +} else if (arguments.all) { + vueParser.parseFilesGlob('{ee/app,app}/assets/javascripts/**/*.{js,vue}').then(() => printJson()); +} else { + console.warn('ERROR: Please use the script correctly:'); + arguments.outputHelp(); + process.exit(1); +} diff --git a/spec/javascripts/locale/ensure_single_line_spec.js b/spec/javascripts/locale/ensure_single_line_spec.js new file mode 100644 index 00000000000..bbefa8f40f3 --- /dev/null +++ b/spec/javascripts/locale/ensure_single_line_spec.js @@ -0,0 +1,35 @@ +import ensureSingleLine from '~/locale/ensure_single_line'; + +describe('locale', () => { + describe('ensureSingleLine', () => { + it('should remove newlines at the start of the string', () => { + const result = 'Test'; + expect(ensureSingleLine(`\n${result}`)).toBe(result); + expect(ensureSingleLine(`\t\n\t${result}`)).toBe(result); + expect(ensureSingleLine(`\r\n${result}`)).toBe(result); + expect(ensureSingleLine(`\r\n ${result}`)).toBe(result); + expect(ensureSingleLine(`\r ${result}`)).toBe(result); + expect(ensureSingleLine(` \n ${result}`)).toBe(result); + }); + + it('should remove newlines at the end of the string', () => { + const result = 'Test'; + expect(ensureSingleLine(`${result}\n`)).toBe(result); + expect(ensureSingleLine(`${result}\t\n\t`)).toBe(result); + expect(ensureSingleLine(`${result}\r\n`)).toBe(result); + expect(ensureSingleLine(`${result}\r`)).toBe(result); + expect(ensureSingleLine(`${result} \r`)).toBe(result); + expect(ensureSingleLine(`${result} \r\n `)).toBe(result); + }); + + it('should replace newlines in the middle of the string with a single space', () => { + const result = 'Test'; + expect(ensureSingleLine(`${result}\n${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result}\t\n\t${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result}\r\n${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result}\r${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result} \r${result}`)).toBe(`${result} ${result}`); + expect(ensureSingleLine(`${result} \r\n ${result}`)).toBe(`${result} ${result}`); + }); + }); +}); diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 67f6a9629d9..0423fcb6ec4 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -1104,7 +1104,7 @@ export const collapsedSystemNotes = [ resolvable: false, noteable_iid: 12, note: 'changed the description', - note_html: '\n <p dir="auto">changed the description 2 times within 1 minute </p>', + note_html: ' <p dir="auto">changed the description 2 times within 1 minute </p>', current_user: { can_edit: false, can_award_emoji: true }, resolved: false, resolved_by: null, diff --git a/spec/javascripts/vue_shared/translate_spec.js b/spec/javascripts/vue_shared/translate_spec.js index cbb3cbdff46..adb5ff682f0 100644 --- a/spec/javascripts/vue_shared/translate_spec.js +++ b/spec/javascripts/vue_shared/translate_spec.js @@ -1,88 +1,249 @@ import Vue from 'vue'; -import Translate from '~/vue_shared/translate'; +import Jed from 'jed'; -Vue.use(Translate); +import locale from '~/locale'; +import Translate from '~/vue_shared/translate'; +import { trimText } from 'spec/helpers/vue_component_helper'; describe('Vue translate filter', () => { let el; + const createTranslationMock = (key, ...translations) => { + const fakeLocale = new Jed({ + domain: 'app', + locale_data: { + app: { + '': { + domain: 'app', + lang: 'vo', + plural_forms: 'nplurals=2; plural=(n != 1);', + }, + [key]: translations, + }, + }, + }); + + // eslint-disable-next-line no-underscore-dangle + locale.__Rewire__('locale', fakeLocale); + }; + + afterEach(() => { + // eslint-disable-next-line no-underscore-dangle + locale.__ResetDependency__('locale'); + }); + beforeEach(() => { + Vue.use(Translate); + el = document.createElement('div'); document.body.appendChild(el); }); - it('translate single text', (done) => { - const comp = new Vue({ + it('translate singular text (`__`)', done => { + const key = 'singular'; + const translation = 'singular_translated'; + createTranslationMock(key, translation); + + const vm = new Vue({ + el, + template: ` + <span> + {{ __('${key}') }} + </span> + `, + }).$mount(); + + Vue.nextTick(() => { + expect(trimText(vm.$el.textContent)).toBe(translation); + + done(); + }); + }); + + it('translate plural text (`n__`) without any substituting text', done => { + const key = 'plural'; + const translationPlural = 'plural_multiple translation'; + createTranslationMock(key, 'plural_singular translation', translationPlural); + + const vm = new Vue({ el, template: ` <span> - {{ __('testing') }} + {{ n__('${key}', 'plurals', 2) }} </span> `, }).$mount(); Vue.nextTick(() => { - expect( - comp.$el.textContent.trim(), - ).toBe('testing'); + expect(trimText(vm.$el.textContent)).toBe(translationPlural); done(); }); }); - it('translate plural text with single count', (done) => { - const comp = new Vue({ + describe('translate plural text (`n__`) with substituting %d', () => { + const key = '%d day'; + + beforeEach(() => { + createTranslationMock(key, '%d singular translated', '%d plural translated'); + }); + + it('and n === 1', done => { + const vm = new Vue({ + el, + template: ` + <span> + {{ n__('${key}', '%d days', 1) }} + </span> + `, + }).$mount(); + + Vue.nextTick(() => { + expect(trimText(vm.$el.textContent)).toBe('1 singular translated'); + + done(); + }); + }); + + it('and n > 1', done => { + const vm = new Vue({ + el, + template: ` + <span> + {{ n__('${key}', '%d days', 2) }} + </span> + `, + }).$mount(); + + Vue.nextTick(() => { + expect(trimText(vm.$el.textContent)).toBe('2 plural translated'); + + done(); + }); + }); + }); + + describe('translates text with context `s__`', () => { + const key = 'Context|Foobar'; + const translation = 'Context|Foobar translated'; + const expectation = 'Foobar translated'; + + beforeEach(() => { + createTranslationMock(key, translation); + }); + + it('and using two parameters', done => { + const vm = new Vue({ + el, + template: ` + <span> + {{ s__('Context', 'Foobar') }} + </span> + `, + }).$mount(); + + Vue.nextTick(() => { + expect(trimText(vm.$el.textContent)).toBe(expectation); + + done(); + }); + }); + + it('and using the pipe syntax', done => { + const vm = new Vue({ + el, + template: ` + <span> + {{ s__('${key}') }} + </span> + `, + }).$mount(); + + Vue.nextTick(() => { + expect(trimText(vm.$el.textContent)).toBe(expectation); + + done(); + }); + }); + }); + + it('translate multi line text', done => { + const translation = 'multiline string translated'; + createTranslationMock('multiline string', translation); + + const vm = new Vue({ el, template: ` <span> - {{ n__('%d day', '%d days', 1) }} + {{ __(\` + multiline + string + \`) }} </span> `, }).$mount(); Vue.nextTick(() => { - expect( - comp.$el.textContent.trim(), - ).toBe('1 day'); + expect(trimText(vm.$el.textContent)).toBe(translation); done(); }); }); - it('translate plural text with multiple count', (done) => { - const comp = new Vue({ + it('translate pluralized multi line text', done => { + const translation = 'multiline string plural'; + + createTranslationMock('multiline string', 'multiline string singular', translation); + + const vm = new Vue({ el, template: ` <span> - {{ n__('%d day', '%d days', 2) }} + {{ n__( + \` + multiline + string + \`, + \` + multiline + strings + \`, + 2 + ) }} </span> `, }).$mount(); Vue.nextTick(() => { - expect( - comp.$el.textContent.trim(), - ).toBe('2 days'); + expect(trimText(vm.$el.textContent)).toBe(translation); done(); }); }); - it('translate plural without replacing any text', (done) => { - const comp = new Vue({ + it('translate pluralized multi line text with context', done => { + const translation = 'multiline string with context'; + + createTranslationMock('Context| multiline string', translation); + + const vm = new Vue({ el, template: ` <span> - {{ n__('day', 'days', 2) }} + {{ s__( + \` + Context| + multiline + string + \` + ) }} </span> `, }).$mount(); Vue.nextTick(() => { - expect( - comp.$el.textContent.trim(), - ).toBe('days'); + expect(trimText(vm.$el.textContent)).toBe(translation); done(); }); diff --git a/yarn.lock b/yarn.lock index c1e9d0ab73e..4326245d2ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -94,10 +94,34 @@ version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" +"@types/events@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" + +"@types/glob@^5": + version "5.0.35" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + "@types/jquery@^2.0.40": version "2.0.48" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef" +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + +"@types/node@*": + version "10.5.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.5.2.tgz#f19f05314d5421fe37e74153254201a7bf00a707" + +"@types/parse5@^5": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.0.tgz#9ae2106efc443d7c1e26570aa8247828c9c80f11" + "@vue/component-compiler-utils@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-1.2.1.tgz#3d543baa75cfe5dab96e29415b78366450156ef6" @@ -2099,6 +2123,10 @@ css-loader@^1.0.0: postcss-value-parser "^3.3.0" source-list-map "^2.0.0" +css-selector-parser@^1.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.3.0.tgz#5f1ad43e2d8eefbfdc304fcd39a521664943e3eb" + css-selector-tokenizer@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" @@ -3520,6 +3548,26 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +gettext-extractor-vue@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.1.tgz#69d2737eb8f1938803ffcf9317133ed59fb2372f" + dependencies: + bluebird "^3.5.1" + glob "^7.1.2" + vue-template-compiler "^2.5.0" + +gettext-extractor@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.3.2.tgz#d5172ba8d175678bd40a5abe7f908fa2a9d9473b" + dependencies: + "@types/glob" "^5" + "@types/parse5" "^5" + css-selector-parser "^1.3" + glob "5 - 7" + parse5 "^5" + pofile "^1" + typescript "^2" + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -3527,24 +3575,24 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^5.0.15: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" +"glob@5 - 7", glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: + fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "2 || 3" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" dependencies: - fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "2 || 3" once "^1.3.0" path-is-absolute "^1.0.0" @@ -5750,6 +5798,10 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse5@^5: + version "5.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.0.0.tgz#4d02710d44f3c3846197a11e205d4ef17842b81a" + parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -5888,6 +5940,10 @@ pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" +pofile@^1: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954" + popper.js@^1.12.9, popper.js@^1.14.3: version "1.14.3" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" @@ -7460,6 +7516,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typescript@^2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" + uglify-es@^3.3.4: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" @@ -7756,7 +7816,7 @@ vue-style-loader@^4.1.0: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@^2.5.16: +vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.16: version "2.5.16" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.16.tgz#93b48570e56c720cdf3f051cc15287c26fbd04cb" dependencies: -- cgit v1.2.1 From 8b03ec1fdf4902ff27e7ad5f77203a6ffe0b93b5 Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Thu, 16 Aug 2018 14:13:26 -0500 Subject: port of EE changes --- app/views/projects/mirrors/_mirror_repos.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index 53387b3a50c..c6764c7607a 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -1,7 +1,7 @@ - expanded = Rails.env.test? - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') -%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) } +%section.settings.project-mirror-settings.js-mirror-settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) } .settings-header %h4= _('Mirroring repositories') %button.btn.js-settings-toggle -- cgit v1.2.1 From 696a5fce687364ec718eeef44d923a686c9e1624 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto <brodock@gmail.com> Date: Wed, 15 Aug 2018 01:39:59 +0200 Subject: Refactor Rainbow usage in specs We don't test any specific string generated by it yet, so there is no point in keeping it enabled when we are disabling it everywhere we test its output. --- spec/lib/gitlab/bare_repository_import/importer_spec.rb | 4 ---- spec/lib/system_check/simple_executor_spec.rb | 9 --------- spec/spec_helper.rb | 1 + 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index 6e21c846c0a..3c63e601abc 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -10,9 +10,6 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do subject(:importer) { described_class.new(admin, bare_repository) } before do - @rainbow = Rainbow.enabled - Rainbow.enabled = false - allow(described_class).to receive(:log) end @@ -20,7 +17,6 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do FileUtils.rm_rf(base_dir) TestEnv.clean_test_path ensure_seeds - Rainbow.enabled = @rainbow end shared_examples 'importing a repository' do diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb index 9da3648400e..e71e9da369d 100644 --- a/spec/lib/system_check/simple_executor_spec.rb +++ b/spec/lib/system_check/simple_executor_spec.rb @@ -98,15 +98,6 @@ describe SystemCheck::SimpleExecutor do end end - before do - @rainbow = Rainbow.enabled - Rainbow.enabled = false - end - - after do - Rainbow.enabled = @rainbow - end - describe '#component' do it 'returns stored component name' do expect(subject.component).to eq('Test') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f4441a6b700..a15a46a9534 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,6 +29,7 @@ end # require rainbow gem String monkeypatch, so we can test SystemChecks require 'rainbow/ext/string' +Rainbow.enabled = false # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. -- cgit v1.2.1 From 1a54986c166fb13a6a27afcafaa055e1a1749e38 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto <brodock@gmail.com> Date: Wed, 15 Aug 2018 01:40:29 +0200 Subject: Refactor SiteStatistics to extract refresh logic into a rake task --- app/models/site_statistic.rb | 14 -------------- lib/tasks/gitlab/site_statistics.rake | 23 +++++++++++++++++++++++ spec/models/site_statistic_spec.rb | 12 ------------ spec/tasks/gitlab/site_statistics_rake_spec.rb | 24 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 lib/tasks/gitlab/site_statistics.rake create mode 100644 spec/tasks/gitlab/site_statistics_rake_spec.rb diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb index 2530a9d8b8e..48324570f0b 100644 --- a/app/models/site_statistic.rb +++ b/app/models/site_statistic.rb @@ -73,18 +73,4 @@ class SiteStatistic < ActiveRecord::Base super end - - def self.recalculate_counters! - transaction do - # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967 - ActiveRecord::Base.connection.execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql? - self.update_all('repositories_count = (SELECT COUNT(*) FROM projects)') - end - - transaction do - # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967 - ActiveRecord::Base.connection.execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql? - self.update_all('wikis_count = (SELECT COUNT(*) FROM project_features WHERE wiki_access_level != 0)') - end - end end diff --git a/lib/tasks/gitlab/site_statistics.rake b/lib/tasks/gitlab/site_statistics.rake new file mode 100644 index 00000000000..7d24ec72a9d --- /dev/null +++ b/lib/tasks/gitlab/site_statistics.rake @@ -0,0 +1,23 @@ +namespace :gitlab do + desc "GitLab | Refresh Site Statistics counters" + task refresh_site_statistics: :environment do + puts 'Updating Site Statistics counters: ' + + print '* Repositories... ' + SiteStatistic.transaction do + # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967 + ActiveRecord::Base.connection.execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql? + SiteStatistic.update_all('repositories_count = (SELECT COUNT(*) FROM projects)') + end + puts 'OK!'.color(:green) + + print '* Wikis... ' + SiteStatistic.transaction do + # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967 + ActiveRecord::Base.connection.execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql? + SiteStatistic.update_all('wikis_count = (SELECT COUNT(*) FROM project_features WHERE wiki_access_level != 0)') + end + puts 'OK!'.color(:green) + puts + end +end diff --git a/spec/models/site_statistic_spec.rb b/spec/models/site_statistic_spec.rb index 49b54fb6994..9b056fbf332 100644 --- a/spec/models/site_statistic_spec.rb +++ b/spec/models/site_statistic_spec.rb @@ -80,16 +80,4 @@ describe SiteStatistic do end end end - - describe '.recalculate_counters!' do - it 'recalculates existing counters' do - create(:project) - described_class.fetch.update(repositories_count: 0, wikis_count: 0) - - described_class.recalculate_counters! - - expect(described_class.fetch.repositories_count).to eq(1) - expect(described_class.fetch.wikis_count).to eq(1) - end - end end diff --git a/spec/tasks/gitlab/site_statistics_rake_spec.rb b/spec/tasks/gitlab/site_statistics_rake_spec.rb new file mode 100644 index 00000000000..20f0df65e63 --- /dev/null +++ b/spec/tasks/gitlab/site_statistics_rake_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +require 'rake_helper' + +describe 'rake gitlab:refresh_site_statistics' do + before do + Rake.application.rake_require 'tasks/gitlab/site_statistics' + + create(:project) + SiteStatistic.fetch.update(repositories_count: 0, wikis_count: 0) + end + + let(:task) { 'gitlab:refresh_site_statistics' } + + it 'recalculates existing counters' do + run_rake_task(task) + + expect(SiteStatistic.fetch.repositories_count).to eq(1) + expect(SiteStatistic.fetch.wikis_count).to eq(1) + end + + it 'displays message listing counters' do + expect { run_rake_task(task) }.to output(/Updating Site Statistics counters:.* Repositories\.\.\. OK!.* Wikis\.\.\. OK!/m).to_stdout + end +end -- cgit v1.2.1 From 7336bd4843521956997c80a10705a83083eb122b Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto <brodock@gmail.com> Date: Wed, 15 Aug 2018 04:38:39 +0200 Subject: Add site statistics recount to the post migiration --- .../20180809195358_migrate_null_wiki_access_levels.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb b/db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb index d3557efecbd..0a0a33299e4 100644 --- a/db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb +++ b/db/post_migrate/20180809195358_migrate_null_wiki_access_levels.rb @@ -5,6 +5,8 @@ class MigrateNullWikiAccessLevels < ActiveRecord::Migration DOWNTIME = false + disable_ddl_transaction! + class ProjectFeature < ActiveRecord::Base include EachBatch @@ -15,6 +17,13 @@ class MigrateNullWikiAccessLevels < ActiveRecord::Migration ProjectFeature.where(wiki_access_level: nil).each_batch do |relation| relation.update_all(wiki_access_level: 20) end + + # We need to re-count wikis as previous attempt was not considering the NULLs. + transaction do + execute('SET LOCAL statement_timeout TO 0') if Gitlab::Database.postgresql? # see https://gitlab.com/gitlab-org/gitlab-ce/issues/48967 + + execute("UPDATE site_statistics SET wikis_count = (SELECT COUNT(*) FROM project_features WHERE wiki_access_level != 0)") + end end def down -- cgit v1.2.1 From d57b92b2ae088dbaed4e8bf169fce6f6f5fb728f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray <annabel.dunstone@gmail.com> Date: Thu, 16 Aug 2018 14:31:53 -0500 Subject: Remove more variable reassignments and unused styles --- app/assets/stylesheets/framework/awards.scss | 4 ++-- app/assets/stylesheets/framework/common.scss | 19 ++++----------- app/assets/stylesheets/framework/files.scss | 10 ++++---- app/assets/stylesheets/framework/issue_box.scss | 2 +- app/assets/stylesheets/framework/lists.scss | 18 +++++++------- app/assets/stylesheets/framework/mixins.scss | 2 +- app/assets/stylesheets/framework/typography.scss | 2 +- app/assets/stylesheets/framework/variables.scss | 29 +---------------------- app/assets/stylesheets/pages/boards.scss | 2 +- app/assets/stylesheets/pages/cycle_analytics.scss | 2 +- app/assets/stylesheets/pages/diff.scss | 2 +- app/assets/stylesheets/pages/events.scss | 4 ++-- app/assets/stylesheets/pages/groups.scss | 4 ++-- app/assets/stylesheets/pages/issuable.scss | 12 +++++----- app/assets/stylesheets/pages/profile.scss | 6 ++--- app/assets/stylesheets/pages/projects.scss | 6 ++--- app/assets/stylesheets/pages/settings.scss | 8 +++---- app/assets/stylesheets/pages/todos.scss | 6 ++--- app/assets/stylesheets/pages/tree.scss | 8 +++---- 19 files changed, 55 insertions(+), 91 deletions(-) diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index 8d11b92cf88..a265e4206f1 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -141,8 +141,8 @@ &:hover, &:active, &.is-active { - background-color: $row-hover; - border-color: $row-hover-border; + background-color: $blue-50; + border-color: $blue-200; box-shadow: none; outline: 0; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 48a87ea8616..79ca6e61e9a 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -71,7 +71,7 @@ pre { } &.card.card-body-pre { - border: 1px solid $well-pre-bg; + border: 1px solid $gray-darker; background: $gray-light; border-radius: 0; color: $well-pre-color; @@ -233,7 +233,7 @@ li.note { .error-message { padding: 10px; - background: $error-bg; + background: $red-400; margin: 0; color: $white-light; @@ -244,11 +244,11 @@ li.note { } .warning_message { - border-left: 4px solid $warning-message-border; - color: $warning-message-color; + border-left: 4px solid $orange-200; + color: $orange-700; padding: 10px; margin-bottom: 10px; - background: $warning-message-bg; + background: $orange-100; padding-left: 20px; &.centered { @@ -348,15 +348,6 @@ img.emoji { } } -.profiler-results { - top: 73px !important; - - .profiler-button, - .profiler-controls { - border-color: $profiler-border !important; - } -} - .dropzone .dz-preview .dz-progress { border-color: $border-color !important; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 54882633fea..3bdf5bfc93a 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -112,7 +112,7 @@ &.image_file, &.video { - background: $file-image-bg; + background: $gray-darker; text-align: center; padding: 30px; @@ -131,7 +131,7 @@ } &.blob-no-preview { - background: $blob-bg; + background: $gray-darker; text-shadow: 0 1px 2px $white-light; padding: 100px 0; } @@ -146,7 +146,7 @@ } tr { - border-bottom: 1px solid $blame-border; + border-bottom: 1px solid $gray-darker; &:last-child { border-bottom: 0; @@ -211,7 +211,7 @@ } &.logs { - background: $logs-bg; + background: $gray-darker; max-height: 700px; overflow-y: auto; @@ -233,7 +233,7 @@ } &:hover { - background: $row-hover; + background: $blue-50; } } } diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 86de88729ee..da5f80d9d37 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -39,7 +39,7 @@ } &.status-box-expired { - background-color: $issue-status-expired; + background-color: $orange-500; } &.status-box-upcoming { diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 4b67eab05b3..fdc0454d837 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -21,11 +21,11 @@ } &.disabled { - color: $list-text-disabled-color; + color: $gl-text-color-tertiary; } &:not(.ui-sort-disabled):hover { - background: $row-hover; + background: $blue-50; } &.unstyled { @@ -35,12 +35,12 @@ } &.warning-row { - background-color: $list-warning-row-bg; - border-color: $list-warning-row-border; - color: $list-warning-row-color; + background-color: $orange-100; + border-color: $orange-200; + color: $orange-700; &:hover { - background: $list-warning-row-bg; + background: $orange-100; } } @@ -73,7 +73,7 @@ } .card.card-body-title { - font-size: $list-font-size; + font-size: $gl-font-size; line-height: 18px; } } @@ -109,8 +109,8 @@ ul.content-list { li { border-color: $white-normal; - font-size: $list-font-size; - color: $list-text-color; + font-size: $gl-font-size; + color: $gl-text-color; &.no-description { .title { diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 98bf26a5222..7edb89ce6f3 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -50,7 +50,7 @@ @include clearfix; padding: 10px 0; - border-bottom: 1px solid $list-border-light; + border-bottom: 1px solid $gray-darker; display: block; margin: 0; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index eccc814b747..5c6110737a4 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -7,7 +7,7 @@ } a { - color: $md-link-color; + color: $blue-600; } img:not(.emoji) { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 866cb88ba5b..96508a71bd8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -203,21 +203,12 @@ $gl-header-color: #4c4e54; /* * Lists */ -$list-font-size: $gl-font-size; -$list-title-color: $gl-text-color; -$list-text-color: $gl-text-color; -$list-text-disabled-color: $gl-text-color-tertiary; -$list-border-light: #eee; $list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; -$list-warning-row-bg: $orange-100; -$list-warning-row-border: $orange-200; -$list-warning-row-color: $orange-700; /* * Markdown */ -$md-link-color: $blue-600; $md-area-border: #ddd; /* @@ -249,8 +240,6 @@ $gl-bar-padding: 3px; /* * Misc */ -$row-hover: $blue-50; -$row-hover-border: $blue-200; $progress-color: #c0392b; $header-height: 40px; $ide-statusbar-height: 25px; @@ -258,19 +247,13 @@ $fixed-layout-width: 1280px; $limited-layout-width: 990px; $container-text-max-width: 540px; $gl-avatar-size: 40px; -$error-exclamation-point: $red-500; $border-radius-default: 4px; $border-radius-small: 2px; $settings-icon-size: 18px; -$provider-btn-not-active-color: $blue-500; -$link-underline-blue: $blue-500; -$active-item-blue: $blue-500; $layout-link-gray: #7e7c7c; $btn-side-margin: 10px; $btn-sm-side-margin: 7px; $btn-margin-5: 5px; -$issue-status-expired: $orange-500; -$issuable-sidebar-color: $gl-text-color-secondary; $sidebar-block-hover-color: #ebebeb; $group-path-color: #999; $namespace-kind-color: #aaa; @@ -292,7 +275,6 @@ $breadcrumb-min-height: 48px; * Common component specific colors */ $hint-color: #999; -$well-pre-bg: #eee; $well-pre-color: #555; $loading-color: #555; $update-author-color: #999; @@ -301,10 +283,6 @@ $user-mention-bg-hover: rgba($blue-500, 0.15); $time-color: #999; $project-member-show-color: #aaa; $gl-promo-color: #aaa; -$error-bg: $red-400; -$warning-message-bg: $orange-100; -$warning-message-border: $orange-200; -$warning-message-color: $orange-700; $control-group-descr-color: #666; $table-permission-x-bg: #d9edf7; $username-color: #666; @@ -374,7 +352,7 @@ $dropdown-min-height: 40px; $dropdown-max-height: 312px; $dropdown-vertical-offset: 4px; $dropdown-link-color: #555; -$dropdown-link-hover-bg: $row-hover; +$dropdown-link-hover-bg: $blue-50; $dropdown-empty-row-bg: rgba(#000, 0.04); $dropdown-border-color: $border-color; $dropdown-shadow-color: rgba(#000, 0.1); @@ -592,11 +570,7 @@ $events-body-border: #ddd; /* * Files */ -$file-image-bg: #eee; -$blob-bg: #eee; -$blame-border: #eee; $blame-line-numbers-border: #ddd; -$logs-bg: #eee; $logs-li-color: #888; $logs-p-color: #333; @@ -667,7 +641,6 @@ $login-devise-error-color: $red-700; * Nav */ $nav-link-gray: #959494; -$nav-badge-bg: #eee; $nav-toggle-gray: #666; /* diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 91f470ca709..69d7de886b4 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -288,7 +288,7 @@ &.is-active, &.is-active .board-card-assignee:hover a { - background-color: $row-hover; + background-color: $blue-50; } .badge { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index bba9f38d3dd..bc4c90711d7 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -165,7 +165,7 @@ border-right-color: transparent; border-top-color: $border-color; border-bottom-color: $border-color; - box-shadow: inset 2px 0 0 0 $active-item-blue; + box-shadow: inset 2px 0 0 0 $blue-500; .stage-name { font-weight: $gl-font-weight-bold; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 47778110bae..a999a70693e 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -186,7 +186,7 @@ } .image { - background: $file-image-bg; + background: $gray-darker; text-align: center; padding: 30px; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index f79586b68b9..da0c9b44498 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -6,7 +6,7 @@ font-size: $gl-font-size; padding: $gl-padding-top 0 $gl-padding-top 40px; border-bottom: 1px solid $white-normal; - color: $list-text-color; + color: $gl-text-color; position: relative; &.event-inline { @@ -58,7 +58,7 @@ .event-title { @include str-truncated(calc(100% - 174px)); font-weight: $gl-font-weight-bold; - color: $list-text-color; + color: $gl-text-color; } .event-body { diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index fa8a0f26b5d..60b4d39bb1a 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -385,8 +385,8 @@ padding: $gl-padding-top; &:hover { - border-color: $row-hover-border; - background-color: $row-hover; + border-color: $blue-200; + background-color: $blue-50; cursor: pointer; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index d16a63d009a..6f0f82964c8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -423,10 +423,10 @@ width: 100%; text-align: center; margin-bottom: 10px; - color: $issuable-sidebar-color; + color: $gl-text-color-secondary; svg { - fill: $issuable-sidebar-color; + fill: $gl-text-color-secondary; } &:hover:not(.disabled), @@ -457,14 +457,14 @@ } .avatar-counter:hover { - color: $issuable-sidebar-color; - border-color: $issuable-sidebar-color; + color: $gl-text-color-secondary; + border-color: $gl-text-color-secondary; } .btn-clipboard { border: 0; background: transparent; - color: $issuable-sidebar-color; + color: $gl-text-color-secondary; &:hover { color: $gl-text-color; @@ -821,7 +821,7 @@ svg { width: 16px; height: 16px; - fill: $issuable-sidebar-color; + fill: $gl-text-color-secondary; } &:hover svg { diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index b45e305897c..17f34319050 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -20,7 +20,7 @@ .account-btn-link, .profile-settings-sidebar a, .settings-sidebar a { - color: $md-link-color; + color: $blue-600; } .private-tokens-reset div.reset-action:not(:first-child) { @@ -137,7 +137,7 @@ .profile-settings-content { a { - color: $md-link-color; + color: $blue-600; } } @@ -170,7 +170,7 @@ background-color: $gray-light; &.not-active { - color: $provider-btn-not-active-color; + color: $blue-500; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index fffb440027c..c11916454c8 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -449,8 +449,8 @@ &:hover:not(.disabled), &.forked { - background-color: $row-hover; - border-color: $row-hover-border; + background-color: $blue-50; + border-color: $blue-200; } .avatar-container, @@ -894,7 +894,7 @@ pre.light-well { .cannot-be-merged, .cannot-be-merged:hover { - color: $error-exclamation-point; + color: $red-500; margin-top: 2px; } diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index fb03970f64f..5aa4cdec9c3 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -113,9 +113,9 @@ .settings-message { padding: 5px; line-height: 1.3; - color: $warning-message-color; - background-color: $warning-message-bg; - border: 1px solid $warning-message-border; + color: $orange-700; + background-color: $orange-100; + border: 1px solid $orange-200; border-radius: $border-radius-base; } @@ -303,7 +303,7 @@ } .mirror-error-badge { - background-color: $error-bg; + background-color: $red-400; border-radius: $border-radius-default; color: $white-light; } diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 010a2c05a1c..5d3b7b21ce4 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -12,8 +12,8 @@ flex-direction: row; &:hover { - background-color: $row-hover; - border-color: $row-hover-border; + background-color: $blue-50; + border-color: $blue-200; cursor: pointer; } @@ -22,7 +22,7 @@ border-bottom: 1px solid transparent; &:hover { - border-color: $row-hover-border; + border-color: $blue-200; } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 1cc26d40ba9..dc5ca78ff58 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -111,9 +111,9 @@ &:hover:not(.tree-truncated-warning) { td { - background-color: $row-hover; - border-top: 1px solid $row-hover-border; - border-bottom: 1px solid $row-hover-border; + background-color: $blue-50; + border-top: 1px solid $blue-200; + border-bottom: 1px solid $blue-200; cursor: pointer; } } @@ -229,7 +229,7 @@ .upload-link { font-weight: $gl-font-weight-normal; - color: $md-link-color; + color: $blue-600; } .repo-charts { -- cgit v1.2.1 From 95644413e5ca7ff1e1f596106eced99e0dd19157 Mon Sep 17 00:00:00 2001 From: Alexander Popov <alex.wayfer@gmail.com> Date: Thu, 16 Aug 2018 17:21:50 +0300 Subject: Add note about Noto Color Emoji font support by default In modern Linux distros. Source: https://www.omgubuntu.co.uk/2018/06/use-emoji-linux-ubuntu-apps --- doc/user/markdown.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 6856544ae1b..6203561265b 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -234,7 +234,12 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: - Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. + Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. + + On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. + + Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. + Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: @@ -246,7 +251,13 @@ If you are new to this, don't be :fearful:. You can easily join the emoji :famil Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: -Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. +Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. + +On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. + +Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. + + ### Special GitLab References -- cgit v1.2.1 From 84af372fc3d75e151d62102ff3acb42beb62b6a1 Mon Sep 17 00:00:00 2001 From: Drew Blessing <drew@gitlab.com> Date: Thu, 16 Aug 2018 15:15:09 -0500 Subject: Update Rouge to 3.2.1. Includes a critical fix for the Perl Lexer --- Gemfile.lock | 2 +- changelogs/unreleased/rouge_3-2-1.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/rouge_3-2-1.yml diff --git a/Gemfile.lock b/Gemfile.lock index b33dd75c278..1aadc3fd0b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -743,7 +743,7 @@ GEM retriable (3.1.1) rinku (2.0.0) rotp (2.1.2) - rouge (3.2.0) + rouge (3.2.1) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) diff --git a/changelogs/unreleased/rouge_3-2-1.yml b/changelogs/unreleased/rouge_3-2-1.yml new file mode 100644 index 00000000000..b281a4f0e95 --- /dev/null +++ b/changelogs/unreleased/rouge_3-2-1.yml @@ -0,0 +1,5 @@ +--- +title: Update to Rouge 3.2.1, which includes a critical fix to the Perl Lexer +merge_request: 21263 +author: +type: changed -- cgit v1.2.1 From eff99e3f0dfc615e35615225661adea74360c4fc Mon Sep 17 00:00:00 2001 From: gfyoung <gfyoung17+gitlab@gmail.com> Date: Thu, 16 Aug 2018 20:29:14 +0000 Subject: Fix 1px cutoff of emojis --- app/assets/stylesheets/pages/notes.scss | 3 --- changelogs/unreleased/emoji-cutoff-1px.yml | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/emoji-cutoff-1px.yml diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 8d28daac750..2e1b2126887 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -141,9 +141,6 @@ ul.notes { } .note-body { - overflow-x: auto; - overflow-y: hidden; - .note-text { @include md-typography; // Reset ul style types since we're nested inside a ul already diff --git a/changelogs/unreleased/emoji-cutoff-1px.yml b/changelogs/unreleased/emoji-cutoff-1px.yml new file mode 100644 index 00000000000..815d9c177e8 --- /dev/null +++ b/changelogs/unreleased/emoji-cutoff-1px.yml @@ -0,0 +1,5 @@ +--- +title: Fix 1px cutoff of emojis +merge_request: 21180 +author: gfyoung +type: fixed -- cgit v1.2.1 From cd1e6dd37c4f03f9205685eb6f7eb44713ec723e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee> Date: Fri, 17 Aug 2018 05:41:53 +0000 Subject: doc: ci/yaml expire_in: the value is in seconds --- app/views/admin/application_settings/_ci_cd.html.haml | 2 ++ doc/ci/yaml/README.md | 4 +++- doc/user/admin_area/settings/continuous_integration.md | 5 +++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml index 5037017e38a..97be658cd34 100644 --- a/app/views/admin/application_settings/_ci_cd.html.haml +++ b/app/views/admin/application_settings/_ci_cd.html.haml @@ -38,6 +38,8 @@ .form-text.text-muted Set the default expiration time for each job's artifacts. 0 for unlimited. + The default unit is in seconds, but you can define an alternative. For example: + <code>4 mins 2 sec</code>, <code>2h42min</code>. = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration') = f.submit 'Save changes', class: "btn btn-success" diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ef740ab1c5e..abba748db8b 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1075,8 +1075,10 @@ keep artifacts forever. After their expiry, artifacts are deleted hourly by default (via a cron job), and are not accessible anymore. -The value of `expire_in` is an elapsed time. Examples of parsable values: +The value of `expire_in` is an elapsed time in seconds, unless a unit is +provided. Examples of parsable values: +- '42' - '3 mins 4 sec' - '2 hrs 20 min' - '2h20min' diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index eb6f915f3f4..76d9a4ceb03 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -21,8 +21,9 @@ that this setting is set for each job. The default expiration time of the [job artifacts][art-yml] can be set in the Admin area of your GitLab instance. The syntax of duration is described in [artifacts:expire_in][duration-syntax]. The default is `30 days`. Note that -this setting is set for each job. Set it to 0 if you don't want default -expiration. +this setting is set for each job. Set it to `0` if you don't want default +expiration. The default unit is in seconds. + 1. Go to **Admin area > Settings** (`/admin/application_settings`). -- cgit v1.2.1 From 5f21b7972da1b97c065e548562bb69dd257e98b8 Mon Sep 17 00:00:00 2001 From: Marcia Ramos <virtua.creative@gmail.com> Date: Fri, 17 Aug 2018 05:52:27 +0000 Subject: Docs: update Pages IP address change + other DNS updates --- .../project/pages/getting_started_part_three.md | 40 +++++++++++++++------ .../img/dns_add_new_a_record_example_updated.png | Bin 10578 -> 0 bytes .../dns_add_new_a_record_example_updated_2018.png | Bin 0 -> 7704 bytes 3 files changed, 30 insertions(+), 10 deletions(-) delete mode 100644 doc/user/project/pages/img/dns_add_new_a_record_example_updated.png create mode 100644 doc/user/project/pages/img/dns_add_new_a_record_example_updated_2018.png diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index 61af1d2ab27..e1eede8bbed 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -1,5 +1,5 @@ --- -last_updated: 2018-02-16 +last_updated: 2018-08-16 author: Marcia Ramos author_gitlab: marcia level: beginner @@ -28,7 +28,7 @@ Let's start from the beginning with [DNS records](#dns-records). If you already know how they work and want to skip the introduction to DNS, you may be interested in skipping it until the [TL;DR](#tl-dr) section below. -## DNS Records +### DNS Records A Domain Name System (DNS) web service routes visitors to websites by translating domain names (such as `www.example.com`) into the @@ -64,22 +64,28 @@ for the most popular hosting services: If your hosting service is not listed above, you can just try to search the web for `how to add dns record on <my hosting service>`. -### DNS A record +#### DNS A record In case you want to point a root domain (`example.com`) to your GitLab Pages site, deployed to `namespace.gitlab.io`, you need to log into your domain's admin control panel and add a DNS `A` record pointing your domain to Pages' server IP address. For projects on -GitLab.com, this IP is `52.167.214.135`. For projects living in +GitLab.com, this IP is `35.185.44.232`. For projects living in other GitLab instances (CE or EE), please contact your sysadmin asking for this information (which IP address is Pages server running on your instance). **Practical Example:** -![DNS A record pointing to GitLab.com Pages server](img/dns_add_new_a_record_example_updated.png) +![DNS A record pointing to GitLab.com Pages server](img/dns_add_new_a_record_example_updated_2018.png) -### DNS CNAME record +NOTE: **Note:** +Note that if you use your root domain for your GitLab Pages website **only**, and if +your domain registrar supports this feature, you can add a DNS apex `CNAME` +record instead of an `A` record. The main advantage of doing so is that when GitLab Pages +IP on GitLab.com changes for whatever reason, you don't need to update your `A` record. + +#### DNS CNAME record In case you want to point a subdomain (`hello-world.example.com`) to your GitLab Pages site initially deployed to `namespace.gitlab.io`, @@ -112,14 +118,14 @@ If the domain has multiple uses (e.g., you host email on it as well): | From | DNS Record | To | | ---- | ---------- | -- | -| domain.com | A | 52.167.214.135 | +| domain.com | A | 35.185.44.232 | | domain.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff | If the domain is dedicated to GitLab Pages use and no other services run on it: | From | DNS Record | To | | ---- | ---------- | -- | -| subdomain.domain.com | CNAME | gitlab.io | +| subdomain.domain.com | CNAME | namespace.gitlab.io | | _gitlab-pages-verification-code.subdomain.domain.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff | > **Notes**: @@ -129,9 +135,11 @@ If the domain is dedicated to GitLab Pages use and no other services run on it: > - **Do not** add any special chars after the default Pages domain. E.g., **do not** point your `subdomain.domain.com` to `namespace.gitlab.io.` or `namespace.gitlab.io/`. -> - GitLab Pages IP on GitLab.com [has been changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) from `104.208.235.32` to `52.167.214.135`. +> - GitLab Pages IP on GitLab.com [was changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) in 2017 +> - GitLab Pages IP on GitLab.com [has been changed](https://about.gitlab.com/2018/07/19/gcp-move-update/#gitlab-pages-and-custom-domains) +from `52.167.214.135` to `35.185.44.232` in 2018 -## Add your custom domain to GitLab Pages settings +### Add your custom domain to GitLab Pages settings Once you've set the DNS record, you'll need navigate to your project's **Setting > Pages** and click **+ New domain** to add your custom domain to @@ -165,6 +173,18 @@ will fail and attempts to visit your domain will respond with a 404. Read through the [general documentation on GitLab Pages](introduction.md#add-a-custom-domain-to-your-pages-website) to learn more about adding custom domains to GitLab Pages sites. +### Redirecting `www.domain.com` to `domain.com` with Cloudflare + +If you use Cloudflare, you can redirect `www` to `domain.com` without the need of adding both +`www.domain.com` and `domain.com` to GitLab. This happens due to a [Cloudflare feature that creates +a 301 redirect as a "page rule"](https://gitlab.com/gitlab-org/gitlab-ce/issues/48848#note_87314849) for redirecting `www.domain.com` to `domain.com`. In this case, +you can use the following setup: + +- In Cloudflare, create a DNS `A` record pointing `domain.com` to `35.185.44.232` +- In GitLab, add the domain to GitLab Pages +- In Cloudflare, create a DNS `TXT` record to verify your domain +- In Cloudflare, create a DNS `CNAME` record poiting `www` to `domain.com` + ## SSL/TLS Certificates Every GitLab Pages project on GitLab.com will be available under diff --git a/doc/user/project/pages/img/dns_add_new_a_record_example_updated.png b/doc/user/project/pages/img/dns_add_new_a_record_example_updated.png deleted file mode 100644 index 2661a497b91..00000000000 Binary files a/doc/user/project/pages/img/dns_add_new_a_record_example_updated.png and /dev/null differ diff --git a/doc/user/project/pages/img/dns_add_new_a_record_example_updated_2018.png b/doc/user/project/pages/img/dns_add_new_a_record_example_updated_2018.png new file mode 100644 index 00000000000..fa72df66587 Binary files /dev/null and b/doc/user/project/pages/img/dns_add_new_a_record_example_updated_2018.png differ -- cgit v1.2.1 From 2b48ec9011e9a3ce69aaddda2d6794d1622a399e Mon Sep 17 00:00:00 2001 From: Steve Azzopardi <steveazz@outlook.com> Date: Fri, 17 Aug 2018 08:14:25 +0200 Subject: Add beta flag for interactive web terminals --- doc/ci/interactive_web_terminal/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md index 55f43e0765b..507aceb27fa 100644 --- a/doc/ci/interactive_web_terminal/index.md +++ b/doc/ci/interactive_web_terminal/index.md @@ -2,6 +2,10 @@ > Introduced in GitLab 11.3. +CAUTION: **Warning:** +Interactive web terminals are in beta, so they might not work properly and +lack features. For more information [follow issue #25990](https://gitlab.com/gitlab-org/gitlab-ce/issues/25990). + Interactive web terminals give the user access to a terminal in GitLab for running one-of commands for their CI pipeline. -- cgit v1.2.1 From b7407d9bc09ae856d8528373b006e9260f13fcef Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller <bbodenmiller@hotmail.com> Date: Fri, 17 Aug 2018 07:11:34 +0000 Subject: replace nobootwait with nofail missed in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16985 --- doc/administration/high_availability/nfs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index 387c3fb6a5b..cd2284f5f2a 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -55,14 +55,14 @@ Below is an example of an NFS mount point defined in `/etc/fstab` we use on GitLab.com: ``` -10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2 +10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 ``` Notice several options that you should consider using: | Setting | Description | | ------- | ----------- | -| `nobootwait` | Don't halt boot process waiting for this mount to become available +| `nofail` | Don't halt boot process waiting for this mount to become available | `lookupcache=positive` | Tells the NFS client to honor `positive` cache results but invalidates any `negative` cache results. Negative cache results cause problems with Git. Specifically, a `git push` can fail to register uniformly across all NFS clients. The negative cache causes the clients to 'remember' that the files did not exist previously. ## A single NFS mount -- cgit v1.2.1 From 3741fcc3de77f1d3604927f0b14cdb9ddc334712 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller <bbodenmiller@hotmail.com> Date: Fri, 17 Aug 2018 07:14:33 +0000 Subject: use nfs4 --- doc/administration/high_availability/gitlab.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index 637d44d2823..b74554a5598 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -25,11 +25,11 @@ for each GitLab application server in your environment. options. Here is an example snippet to add to `/etc/fstab`: ``` - 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 - 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 - 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 - 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 - 10.1.0.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 + 10.1.0.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 ``` 1. Create the shared directories. These may be different depending on your NFS -- cgit v1.2.1 From fbf618fc3817442ef17848b67738ff456d65e7e4 Mon Sep 17 00:00:00 2001 From: Valery Sizov <valery@gitlab.com> Date: Mon, 13 Aug 2018 14:54:31 +0300 Subject: Fix: Project deletion may not log audit events during user deletion --- app/services/projects/destroy_service.rb | 3 -- app/services/users/destroy_service.rb | 7 ++-- ...y-not-log-audit-events-during-user-deletion.yml | 5 +++ spec/services/users/destroy_service_spec.rb | 39 ++++++++++++++-------- spec/workers/project_destroy_worker_spec.rb | 7 ---- 5 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-user-deletion.yml diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 46a8a5e4d98..76e22507698 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -83,9 +83,6 @@ module Projects end def remove_repository(path) - # Skip repository removal. We use this flag when remove user or group - return true if params[:skip_repo] == true - # There is a possibility project does not have repository or wiki return true unless repo_exists?(path) diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 4bc78b5b64e..73fa6089945 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -2,6 +2,8 @@ module Users class DestroyService + DestroyError = Class.new(StandardError) + attr_accessor :current_user def initialize(current_user) @@ -46,9 +48,8 @@ module Users namespace.prepare_for_destroy user.personal_projects.each do |project| - # Skip repository removal because we remove directory with namespace - # that contain all this repositories - ::Projects::DestroyService.new(project, current_user, skip_repo: project.legacy_storage?).execute + success = ::Projects::DestroyService.new(project, current_user).execute + raise DestroyError, "Project #{project.id} can't be deleted" unless success end yield(user) if block_given? diff --git a/changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-user-deletion.yml b/changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-user-deletion.yml new file mode 100644 index 00000000000..a8e3d590a4a --- /dev/null +++ b/changelogs/unreleased/49796-project-deletion-may-not-log-audit-events-during-user-deletion.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix: Project deletion may not log audit events during user deletion' +merge_request: +author: +type: fixed diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 3bae8bfbd42..83f1495a1c6 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -20,7 +20,7 @@ describe Users::DestroyService do it 'will delete the project' do expect_next_instance_of(Projects::DestroyService) do |destroy_service| - expect(destroy_service).to receive(:execute).once + expect(destroy_service).to receive(:execute).once.and_return(true) end service.execute(user) @@ -35,7 +35,7 @@ describe Users::DestroyService do it 'destroys a project in pending_delete' do expect_next_instance_of(Projects::DestroyService) do |destroy_service| - expect(destroy_service).to receive(:execute).once + expect(destroy_service).to receive(:execute).once.and_return(true) end service.execute(user) @@ -172,23 +172,36 @@ describe Users::DestroyService do end describe "user personal's repository removal" do - before do - perform_enqueued_jobs { service.execute(user) } - end + context 'storages' do + before do + perform_enqueued_jobs { service.execute(user) } + end + + context 'legacy storage' do + let!(:project) { create(:project, :empty_repo, :legacy_storage, namespace: user.namespace) } + + it 'removes repository' do + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey + end + end - context 'legacy storage' do - let!(:project) { create(:project, :empty_repo, :legacy_storage, namespace: user.namespace) } + context 'hashed storage' do + let!(:project) { create(:project, :empty_repo, namespace: user.namespace) } - it 'removes repository' do - expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey + it 'removes repository' do + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey + end end end - context 'hashed storage' do - let!(:project) { create(:project, :empty_repo, namespace: user.namespace) } + context 'repository removal status is taken into account' do + it 'raises exception' do + expect_next_instance_of(::Projects::DestroyService) do |destroy_service| + expect(destroy_service).to receive(:execute).and_return(false) + end - it 'removes repository' do - expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey + expect { service.execute(user) } + .to raise_error(Users::DestroyService::DestroyError, "Project #{project.id} can't be deleted" ) end end end diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb index 42e1d86e3bb..6132f145f8d 100644 --- a/spec/workers/project_destroy_worker_spec.rb +++ b/spec/workers/project_destroy_worker_spec.rb @@ -18,13 +18,6 @@ describe ProjectDestroyWorker do expect(Dir.exist?(path)).to be_falsey end - it 'deletes the project but skips repo deletion' do - subject.perform(project.id, project.owner.id, { "skip_repo" => true }) - - expect(Project.all).not_to include(project) - expect(Dir.exist?(path)).to be_truthy - end - it 'does not raise error when project could not be found' do expect do subject.perform(-1, project.owner.id, {}) -- cgit v1.2.1 From 729de4f1ba04f1a9a44b7f3f3b9bd5fb9165e4ca Mon Sep 17 00:00:00 2001 From: Andreas Brandl <abrandl@gitlab.com> Date: Mon, 23 Jul 2018 16:36:21 +0200 Subject: Add migration to cleanup internal_ids. See https://gitlab.com/gitlab-org/gitlab-ce/issues/49446. --- .../ab-49446-internal-ids-inconsistency.yml | 5 + ...0817_delete_inconsistent_internal_id_records.rb | 47 ++++++++ ...delete_inconsistent_internal_id_records_spec.rb | 119 +++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 changelogs/unreleased/ab-49446-internal-ids-inconsistency.yml create mode 100644 db/post_migrate/20180723130817_delete_inconsistent_internal_id_records.rb create mode 100644 spec/migrations/delete_inconsistent_internal_id_records_spec.rb diff --git a/changelogs/unreleased/ab-49446-internal-ids-inconsistency.yml b/changelogs/unreleased/ab-49446-internal-ids-inconsistency.yml new file mode 100644 index 00000000000..bfea57d79e0 --- /dev/null +++ b/changelogs/unreleased/ab-49446-internal-ids-inconsistency.yml @@ -0,0 +1,5 @@ +--- +title: Add migration to cleanup internal_ids inconsistency. +merge_request: 20926 +author: +type: fixed diff --git a/db/post_migrate/20180723130817_delete_inconsistent_internal_id_records.rb b/db/post_migrate/20180723130817_delete_inconsistent_internal_id_records.rb new file mode 100644 index 00000000000..3b9b95ec9ca --- /dev/null +++ b/db/post_migrate/20180723130817_delete_inconsistent_internal_id_records.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true +class DeleteInconsistentInternalIdRecords < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + # This migration cleans up any inconsistent records in internal_ids. + # + # That is, it deletes records that track a `last_value` that is + # smaller than the maximum internal id (usually `iid`) found in + # the corresponding model records. + + def up + disable_statement_timeout do + delete_internal_id_records('issues', 'project_id') + delete_internal_id_records('merge_requests', 'project_id', 'target_project_id') + delete_internal_id_records('deployments', 'project_id') + delete_internal_id_records('milestones', 'project_id') + delete_internal_id_records('milestones', 'namespace_id', 'group_id') + delete_internal_id_records('ci_pipelines', 'project_id') + end + end + + class InternalId < ActiveRecord::Base + self.table_name = 'internal_ids' + enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4, ci_pipelines: 5 } + end + + private + + def delete_internal_id_records(base_table, scope_column_name, base_scope_column_name = scope_column_name) + sql = <<~SQL + SELECT id FROM ( -- workaround for MySQL + SELECT internal_ids.id FROM ( + SELECT #{base_scope_column_name} AS #{scope_column_name}, max(iid) as maximum_iid from #{base_table} GROUP BY #{scope_column_name} + ) maxima JOIN internal_ids USING (#{scope_column_name}) + WHERE internal_ids.usage=#{InternalId.usages.fetch(base_table)} AND maxima.maximum_iid > internal_ids.last_value + ) internal_ids + SQL + + InternalId.where("id IN (#{sql})").tap do |ids| # rubocop:disable GitlabSecurity/SqlInjection + say "Deleting internal_id records for #{base_table}: #{ids.pluck(:project_id, :last_value)}" unless ids.empty? + end.delete_all + end +end diff --git a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb new file mode 100644 index 00000000000..becb71cf427 --- /dev/null +++ b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true +# rubocop:disable RSpec/FactoriesInMigrationSpecs +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180723130817_delete_inconsistent_internal_id_records.rb') + +describe DeleteInconsistentInternalIdRecords, :migration do + let!(:project1) { create(:project) } + let!(:project2) { create(:project) } + let!(:project3) { create(:project) } + + let(:internal_id_query) { ->(project) { InternalId.where(usage: InternalId.usages[scope.to_s.tableize], project: project) } } + + let(:create_models) do + 3.times { create(scope, project: project1) } + 3.times { create(scope, project: project2) } + 3.times { create(scope, project: project3) } + end + + shared_examples_for 'deleting inconsistent internal_id records' do + before do + create_models + + internal_id_query.call(project1).first.tap do |iid| + iid.last_value = iid.last_value - 2 + # This is an inconsistent record + iid.save! + end + + internal_id_query.call(project3).first.tap do |iid| + iid.last_value = iid.last_value + 2 + # This is a consistent record + iid.save! + end + end + + it "deletes inconsistent issues" do + expect { migrate! }.to change { internal_id_query.call(project1).size }.from(1).to(0) + end + + it "retains consistent issues" do + expect { migrate! }.not_to change { internal_id_query.call(project2).size } + end + + it "retains consistent records, especially those with a greater last_value" do + expect { migrate! }.not_to change { internal_id_query.call(project3).size } + end + end + + context 'for issues' do + let(:scope) { :issue } + it_behaves_like 'deleting inconsistent internal_id records' + end + + context 'for merge_requests' do + let(:scope) { :merge_request } + + let(:create_models) do + 3.times { |i| create(scope, target_project: project1, source_project: project1, source_branch: i.to_s) } + 3.times { |i| create(scope, target_project: project2, source_project: project2, source_branch: i.to_s) } + 3.times { |i| create(scope, target_project: project3, source_project: project3, source_branch: i.to_s) } + end + + it_behaves_like 'deleting inconsistent internal_id records' + end + + context 'for deployments' do + let(:scope) { :deployment } + it_behaves_like 'deleting inconsistent internal_id records' + end + + context 'for milestones (by project)' do + let(:scope) { :milestone } + it_behaves_like 'deleting inconsistent internal_id records' + end + + context 'for ci_pipelines' do + let(:scope) { :ci_pipeline } + it_behaves_like 'deleting inconsistent internal_id records' + end + + context 'for milestones (by group)' do + # milestones (by group) is a little different than all of the other models + let!(:group1) { create(:group) } + let!(:group2) { create(:group) } + let!(:group3) { create(:group) } + + let(:internal_id_query) { ->(group) { InternalId.where(usage: InternalId.usages['milestones'], namespace: group) } } + + before do + 3.times { create(:milestone, group: group1) } + 3.times { create(:milestone, group: group2) } + 3.times { create(:milestone, group: group3) } + + internal_id_query.call(group1).first.tap do |iid| + iid.last_value = iid.last_value - 2 + # This is an inconsistent record + iid.save! + end + + internal_id_query.call(group3).first.tap do |iid| + iid.last_value = iid.last_value + 2 + # This is a consistent record + iid.save! + end + end + + it "deletes inconsistent issues" do + expect { migrate! }.to change { internal_id_query.call(group1).size }.from(1).to(0) + end + + it "retains consistent issues" do + expect { migrate! }.not_to change { internal_id_query.call(group2).size } + end + + it "retains consistent records, especially those with a greater last_value" do + expect { migrate! }.not_to change { internal_id_query.call(group3).size } + end + end +end -- cgit v1.2.1 From da9b3b3d0f5923b6036b71c187db26c30e367a50 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Thu, 16 Aug 2018 16:23:38 +0100 Subject: Creates vue components for stage dropdowns and job list container for job log view --- .../javascripts/jobs/components/jobs_container.vue | 60 ++++++++++ .../jobs/components/stages_dropdown.vue | 97 ++++++++++++++++ changelogs/unreleased/50101-builds-dropdown.yml | 6 + locale/gitlab.pot | 3 + spec/javascripts/jobs/jobs_container_spec.js | 126 +++++++++++++++++++++ spec/javascripts/jobs/stages_dropdown_spec.js | 63 +++++++++++ 6 files changed, 355 insertions(+) create mode 100644 app/assets/javascripts/jobs/components/jobs_container.vue create mode 100644 app/assets/javascripts/jobs/components/stages_dropdown.vue create mode 100644 changelogs/unreleased/50101-builds-dropdown.yml create mode 100644 spec/javascripts/jobs/jobs_container_spec.js create mode 100644 spec/javascripts/jobs/stages_dropdown_spec.js diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue new file mode 100644 index 00000000000..b81109bdd06 --- /dev/null +++ b/app/assets/javascripts/jobs/components/jobs_container.vue @@ -0,0 +1,60 @@ +<script> + import CiIcon from '~/vue_shared/components/ci_icon.vue'; + import Icon from '~/vue_shared/components/icon.vue'; + import tooltip from '~/vue_shared/directives/tooltip'; + + export default { + components: { + CiIcon, + Icon, + }, + directives: { + tooltip, + }, + props: { + jobs: { + type: Array, + required: true, + }, + }, + }; +</script> +<template> + <div class="builds-container"> + <div + class="build-job" + > + <a + v-tooltip + v-for="job in jobs" + :key="job.id" + :href="job.path" + :title="job.tooltip" + :class="{ active: job.active, retried: job.retried }" + > + <icon + v-if="job.active" + name="arrow-right" + class="js-arrow-right" + /> + + <ci-icon :status="job.status" /> + + <span> + <template v-if="job.name"> + {{ job.name }} + </template> + <template v-else> + {{ job.id }} + </template> + </span> + + <icon + v-if="job.retried" + name="retry" + class="js-retry-icon" + /> + </a> + </div> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue new file mode 100644 index 00000000000..d6d64fa32f7 --- /dev/null +++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue @@ -0,0 +1,97 @@ +<script> + import CiIcon from '~/vue_shared/components/ci_icon.vue'; + import Icon from '~/vue_shared/components/icon.vue'; + + import { sprintf, __ } from '~/locale'; + + export default { + components: { + CiIcon, + Icon, + }, + props: { + pipelineId: { + type: Number, + required: true, + }, + pipelinePath: { + type: String, + required: true, + }, + pipelineRef: { + type: String, + required: true, + }, + pipelineRefPath: { + type: String, + required: true, + }, + stages: { + type: Array, + required: true, + }, + pipelineStatus: { + type: Object, + required: true, + }, + }, + data() { + return { + selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'), + }; + }, + computed: { + pipelineLink() { + return sprintf(__('Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}'), { + pipelineLinkStart: `<a href=${this.pipelinePath} class="js-pipeline-path link-commit">`, + pipelineId: this.pipelineId, + pipelineLinkEnd: '</a>', + pipelineLinkRefStart: `<a href=${this.pipelineRefPath} class="link-commit ref-name">`, + pipelineRef: this.pipelineRef, + pipelineLinkRefEnd: '</a>', + }, false); + }, + }, + methods: { + onStageClick(stage) { + // todo: consider moving into store + this.selectedStage = stage.name; + + // update dropdown with jobs + // jobs container is a new component. + this.$emit('requestSidebarStageDropdown', stage); + }, + }, + }; +</script> +<template> + <div class="block-last"> + <ci-icon :status="pipelineStatus" /> + + <p v-html="pipelineLink"></p> + + <div class="dropdown"> + <button + type="button" + data-toggle="dropdown" + > + {{ selectedStage }} + <icon name="chevron-down" /> + </button> + <ul class="dropdown-menu"> + <li + v-for="(stage, index) in stages" + :key="index" + > + <button + type="button" + class="stage-item" + @click="onStageClick(stage)" + > + {{ stage.name }} + </button> + </li> + </ul> + </div> + </div> +</template> diff --git a/changelogs/unreleased/50101-builds-dropdown.yml b/changelogs/unreleased/50101-builds-dropdown.yml new file mode 100644 index 00000000000..9194b0e0d31 --- /dev/null +++ b/changelogs/unreleased/50101-builds-dropdown.yml @@ -0,0 +1,6 @@ +--- +title: Creates vue components for stage dropdowns and job list container for job log + view +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1f61ac872f8..73bff79aabe 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4110,6 +4110,9 @@ msgstr "" msgid "Pipeline" msgstr "" +msgid "Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}" +msgstr "" + msgid "Pipeline Health" msgstr "" diff --git a/spec/javascripts/jobs/jobs_container_spec.js b/spec/javascripts/jobs/jobs_container_spec.js new file mode 100644 index 00000000000..bf52e65cbc8 --- /dev/null +++ b/spec/javascripts/jobs/jobs_container_spec.js @@ -0,0 +1,126 @@ +import Vue from 'vue'; +import component from '~/jobs/components/jobs_container.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Artifacts block', () => { + const Component = Vue.extend(component); + let vm; + + const retried = { + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + path: 'job/233432756', + id: '233432756', + tooltip: 'build - passed', + retried: true, + }; + + const active = { + name: 'test', + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + path: 'job/2322756', + id: '2322756', + tooltip: 'build - passed', + active: true, + }; + + const job = { + name: 'build', + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + path: 'job/232153', + id: '232153', + tooltip: 'build - passed', + }; + + afterEach(() => { + vm.$destroy(); + }); + + it('renders list of jobs', () => { + vm = mountComponent(Component, { + jobs: [job, retried, active], + }); + + expect(vm.$el.querySelectorAll('a').length).toEqual(3); + }); + + it('renders arrow right when job is active', () => { + vm = mountComponent(Component, { + jobs: [active], + }); + + expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull(); + }); + + it('does not render arrow right when job is not active', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull(); + }); + + it('renders job name when present', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name); + expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(job.id); + }); + + it('renders job id when job name is not available', () => { + vm = mountComponent(Component, { + jobs: [retried], + }); + + expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id); + }); + + it('links to the job page', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.path); + }); + + it('renders retry icon when job was retried', () => { + vm = mountComponent(Component, { + jobs: [retried], + }); + + expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull(); + }); + + it('does not render retry icon when job was not retried', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('.js-retry-icon')).toBeNull(); + }); +}); diff --git a/spec/javascripts/jobs/stages_dropdown_spec.js b/spec/javascripts/jobs/stages_dropdown_spec.js new file mode 100644 index 00000000000..d3a5d48f56c --- /dev/null +++ b/spec/javascripts/jobs/stages_dropdown_spec.js @@ -0,0 +1,63 @@ +import Vue from 'vue'; +import component from '~/jobs/components/stages_dropdown.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Artifacts block', () => { + const Component = Vue.extend(component); + let vm; + + beforeEach(() => { + vm = mountComponent(Component, { + pipelineId: 28029444, + pipelinePath: 'pipeline/28029444', + pipelineRef: '50101-truncated-job-information', + pipelineRefPath: 'commits/50101-truncated-job-information', + stages: [ + { + name: 'build', + }, + { + name: 'test', + }, + ], + pipelineStatus: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders pipeline status', () => { + expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull(); + }); + + it('renders pipeline link', () => { + expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual( + 'pipeline/28029444', + ); + }); + + it('renders dropdown with stages', () => { + expect(vm.$el.querySelector('.dropdown button').textContent).toContain('build'); + }); + + it('updates selected stage on click', done => { + vm.$el.querySelectorAll('.stage-item')[1].click(); + vm + .$nextTick() + .then(() => { + expect(vm.$el.querySelector('.dropdown button').textContent).toContain('test'); + }) + .then(done) + .catch(done.fail); + }); +}); -- cgit v1.2.1 From bc53c4f0b6e1fba6d72e9a47f2e8846697f56371 Mon Sep 17 00:00:00 2001 From: Shinya Maeda <shinya@gitlab.com> Date: Fri, 17 Aug 2018 10:32:40 +0000 Subject: Update .gitlab-ci.yml --- .gitlab-ci.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fd02d72b4c2..797a20ef16e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -739,7 +739,7 @@ karma: - chrome_debug.log - coverage-javascript/ -codequality: +code_quality: <<: *dedicated-no-docs-no-db-pull-cache-job image: docker:stable allow_failure: true @@ -757,9 +757,13 @@ codequality: script: # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - - docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code + - docker run + --env SOURCE_CODE="$PWD" + --volume "$PWD":/code + --volume /var/run/docker.sock:/var/run/docker.sock + "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code artifacts: - paths: [codeclimate.json] + paths: [gl-code-quality-report.json] expire_in: 1 week sast: -- cgit v1.2.1 From 42d2d64b84ee555c1c1520576652de43a9ce534a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin <godfat@godfat.org> Date: Fri, 17 Aug 2018 18:44:06 +0800 Subject: Mention how to override CE class methods --- doc/development/ee_features.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 32de741c9fe..1cd873b6fe3 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -258,6 +258,31 @@ end [`extend ::Gitlab::Utils::Override`]: utilities.md#override +##### Overriding CE class methods + +The same applies to class methods, except we want to use +`ActiveSupport::Concern` and put `extend ::Gitlab::Utils::Override` +within the block of `class_methods`. Here's an example: + +```ruby +module EE + module Groups + module GroupMembersController + extend ActiveSupport::Concern + + class_methods do + extend ::Gitlab::Utils::Override + + override :admin_not_required_endpoints + def admin_not_required_endpoints + super.concat(%i[update override]) + end + end + end + end +end +``` + #### Use self-descriptive wrapper methods When it's not possible/logical to modify the implementation of a @@ -665,6 +690,9 @@ module EE extend ActiveSupport::Concern class_methods do + extend ::Gitlab::Utils::Override + + override :update_params_at_least_one_of def update_params_at_least_one_of super.push(*%i[ squash -- cgit v1.2.1 From 3d46f3155ae07d566dfbc72776e0b4a3207a91fc Mon Sep 17 00:00:00 2001 From: Yorick Peterse <yorickpeterse@gmail.com> Date: Tue, 14 Aug 2018 18:07:15 +0200 Subject: Add documentation about reading query plans This adds a database guide on how to read the output of "EXPLAIN" and "EXPLAIN ANALYZE", and how to use this output to understand a query's performance and optimise it. --- doc/development/README.md | 8 +- doc/development/understanding_explain_plans.md | 676 +++++++++++++++++++++++++ 2 files changed, 683 insertions(+), 1 deletion(-) create mode 100644 doc/development/understanding_explain_plans.md diff --git a/doc/development/README.md b/doc/development/README.md index fed3903c771..ee9a9852205 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -55,7 +55,13 @@ description: 'Learn how to contribute to GitLab.' - [Merge request performance guidelines](merge_request_performance_guidelines.md) for ensuring merge requests do not negatively impact GitLab performance -## Databases guides +## Database guides + +### Tooling + +- [Understanding EXPLAIN plans](understanding_explain_plans.md) +- [explain.depesz.com](https://explain.depesz.com/) for visualising the output + of `EXPLAIN` ### Migrations diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md new file mode 100644 index 00000000000..adf8795a5e3 --- /dev/null +++ b/doc/development/understanding_explain_plans.md @@ -0,0 +1,676 @@ +# Understanding EXPLAIN plans + +PostgreSQL allows you to obtain query plans using the `EXPLAIN` command. This +command can be invaluable when trying to determine how a query will perform. +You can use this command directly in your SQL query, as long as the query starts +with it: + +```sql +EXPLAIN +SELECT COUNT(*) +FROM projects +WHERE visibility_level IN (0, 20); +``` + +When running this on GitLab.com, we are presented with the following output: + +``` +Aggregate (cost=922411.76..922411.77 rows=1 width=8) + -> Seq Scan on projects (cost=0.00..908044.47 rows=5746914 width=0) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) +``` + +When using _just_ `EXPLAIN`, PostgreSQL won't actually execute our query, +instead it produces an _estimated_ execution plan based on the available +statistics. This means the actual plan can differ quite a bit. Fortunately, +PostgreSQL provides us with the option to execute the query as well. To do so, +we need to use `EXPLAIN ANALYZE` instead of just `EXPLAIN`: + +```sql +EXPLAIN ANALYZE +SELECT COUNT(*) +FROM projects +WHERE visibility_level IN (0, 20); +``` + +This will produce: + +``` +Aggregate (cost=922420.60..922420.61 rows=1 width=8) (actual time=3428.535..3428.535 rows=1 loops=1) + -> Seq Scan on projects (cost=0.00..908053.18 rows=5746969 width=0) (actual time=0.041..2987.606 rows=5746940 loops=1) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) + Rows Removed by Filter: 65677 +Planning time: 2.861 ms +Execution time: 3428.596 ms +``` + +As we can see this plan is quite different, and includes a lot more data. Let's +discuss this step by step. + +Because `EXPLAIN ANALYZE` executes the query, care should be taken when using a +query that will write data or might time out. If the query modifies data, +consider wrapping it in a transaction that rolls back automatically like so: + +```sql +BEGIN; +EXPLAIN ANALYZE +DELETE FROM users WHERE id = 1; +ROLLBACK; +``` + +The `EXPLAIN` command also takes additional options, such as `BUFFERS`: + +```sql +EXPLAIN (ANALYZE, BUFFERS) +SELECT COUNT(*) +FROM projects +WHERE visibility_level IN (0, 20); +``` + +This will then produce: + +``` +Aggregate (cost=922420.60..922420.61 rows=1 width=8) (actual time=3428.535..3428.535 rows=1 loops=1) + Buffers: shared hit=208846 + -> Seq Scan on projects (cost=0.00..908053.18 rows=5746969 width=0) (actual time=0.041..2987.606 rows=5746940 loops=1) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) + Rows Removed by Filter: 65677 + Buffers: shared hit=208846 +Planning time: 2.861 ms +Execution time: 3428.596 ms +``` + +For more information, refer to the official [EXPLAIN +documentation](https://www.postgresql.org/docs/current/static/sql-explain.html). + +## Nodes + +Every query plan consists of nodes. Nodes can be nested, and are executed from +the inside out. This means that the innermost node is executed before an outer +node. This can be best thought of as nested function calls, returning their +results as they unwind. For example, a plan starting with an `Aggregate` +followed by a `Nested Loop`, followed by an `Index Only scan` can be thought of +as the following Ruby code: + +```ruby +aggregate( + nested_loop( + index_only_scan() + index_only_scan() + ) +) +``` + +Nodes are indicated using a `->` followed by the type of node taken. For +example: + +``` +Aggregate (cost=922411.76..922411.77 rows=1 width=8) + -> Seq Scan on projects (cost=0.00..908044.47 rows=5746914 width=0) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) +``` + +Here the first node executed is `Seq scan on projects`. The `Filter:` is an +additional filter applied to the results of the node. A filter is very similar +to Ruby's `Array#select`: it takes the input rows, applies the filter, and +produces a new list of rows. Once the node is done, we perform the `Aggregate` +above it. + +Nested nodes will look like this: + +``` +Aggregate (cost=176.97..176.98 rows=1 width=8) (actual time=0.252..0.252 rows=1 loops=1) + Buffers: shared hit=155 + -> Nested Loop (cost=0.86..176.75 rows=87 width=0) (actual time=0.035..0.249 rows=36 loops=1) + Buffers: shared hit=155 + -> Index Only Scan using users_pkey on users users_1 (cost=0.43..4.95 rows=87 width=4) (actual time=0.029..0.123 rows=36 loops=1) + Index Cond: (id < 100) + Heap Fetches: 0 + -> Index Only Scan using users_pkey on users (cost=0.43..1.96 rows=1 width=4) (actual time=0.003..0.003 rows=1 loops=36) + Index Cond: (id = users_1.id) + Heap Fetches: 0 +Planning time: 2.585 ms +Execution time: 0.310 ms +``` + +Here we first perform two separate "Index Only" scans, followed by performing a +"Nested Loop" on the result of these two scans. + +## Node statistics + +Each node in a plan has a set of associated statistics, such as the cost, the +number of rows produced, the number of loops performed, and more. For example: + +``` +Seq Scan on projects (cost=0.00..908044.47 rows=5746914 width=0) +``` + +Here we can see that our cost ranges from `0.00..908044.47` (we'll cover this in +a moment), and we estimate (since we're using `EXPLAIN` and not `EXPLAIN +ANALYZE`) a total of 5,746,914 rows to be produced by this node. The `width` +statistics describes the estimated width of each row, in bytes. + +The `costs` field specifies how expensive a node was. The cost is measured in +arbitrary units determined by the query planner's cost parameters. What +influences the costs depends on a variety of settings, such as `seq_page_cost`, +`cpu_tuple_cost`, and various others. +The format of the costs field is as follows: + +``` +STARTUP COST..TOTAL COST +``` + +The startup cost states how expensive it was to start the node, with the total +cost describing how expensive the entire node was. In general: the greater the +values, the more expensive the node. + +When using `EXPLAIN ANALYZE`, these statistics will also include the actual time +(in milliseconds) spent, and other runtime statistics (e.g. the actual number of +produced rows): + +``` +Seq Scan on projects (cost=0.00..908053.18 rows=5746969 width=0) (actual time=0.041..2987.606 rows=5746940 loops=1) +``` + +Here we can see we estimated 5,746,969 rows to be returned, but in reality we +returned 5,746,940 rows. We can also see that _just_ this sequential scan took +2.98 seconds to run. + +Using `EXPLAIN (ANALYZE, BUFFERS)` will also give us information about the +number of rows removed by a filter, the number of buffers used, and more. For +example: + +``` +Seq Scan on projects (cost=0.00..908053.18 rows=5746969 width=0) (actual time=0.041..2987.606 rows=5746940 loops=1) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) + Rows Removed by Filter: 65677 + Buffers: shared hit=208846 +``` + +Here we can see that our filter has to remove 65,677 rows, and that we use +208,846 buffers. Each buffer in PostgreSQL is 8 KB (8192 bytes), meaning our +above node uses *1.6 GB of buffers*. That's a lot! + +## Node types + +There are quite a few different types of nodes, so we only cover some of the +more common ones here. + +A full list of all the available nodes and their descriptions can be found in +the [PostgreSQL source file +"plannodes.h"](https://github.com/postgres/postgres/blob/master/src/include/nodes/plannodes.h) + +### Seq Scan + +A sequential scan over (a chunk of) a database table. This is like using +`Array#each`, but on a database table. Sequential scans can be quite slow when +retrieving lots of rows, so it's best to avoid these for large tables. + +### Index Only Scan + +A scan on an index that did not require fetching anything from the table. In +certain cases an index only scan may still fetch data from the table, in this +case the node will include a `Heap Fetches:` statistic. + +### Index Scan + +A scan on an index that required retrieving some data from the table. + +### Bitmap Index Scan and Bitmap Heap scan + +Bitmap scans fall between sequential scans and index scans. These are typically +used when we would read too much data from an index scan, but too little to +perform a sequential scan. A bitmap scan uses what is known as a [bitmap +index](https://en.wikipedia.org/wiki/Bitmap_index) to perform its work. + +The [source code of PostgreSQL](https://github.com/postgres/postgres/blob/1c2cb2744bf3d8ad751cd5cf3b347f10f48492b3/src/include/nodes/plannodes.h#L446-L457) +states the following on bitmap scans: + +> Bitmap Index Scan delivers a bitmap of potential tuple locations; it does not +> access the heap itself. The bitmap is used by an ancestor Bitmap Heap Scan +> node, possibly after passing through intermediate Bitmap And and/or Bitmap Or +> nodes to combine it with the results of other Bitmap Index Scans. + +### Limit + +Applies a `LIMIT` on the input rows. + +### Sort + +Sorts the input rows as specified using an `ORDER BY` statement. + +### Nested Loop + +A nested loop will execute its child nodes for every row produced by a node that +precedes it. For example: + +``` +-> Nested Loop (cost=0.86..176.75 rows=87 width=0) (actual time=0.035..0.249 rows=36 loops=1) + Buffers: shared hit=155 + -> Index Only Scan using users_pkey on users users_1 (cost=0.43..4.95 rows=87 width=4) (actual time=0.029..0.123 rows=36 loops=1) + Index Cond: (id < 100) + Heap Fetches: 0 + -> Index Only Scan using users_pkey on users (cost=0.43..1.96 rows=1 width=4) (actual time=0.003..0.003 rows=1 loops=36) + Index Cond: (id = users_1.id) + Heap Fetches: 0 +``` + +Here the first child node (`Index Only Scan using users_pkey on users users_1`) +produces 36 rows, and is executed once (`rows=36 loops=1`). The next node +produces 1 row (`rows=1`), but is repeated 36 times (`loops=36`). This is +because the previous node produced 36 rows. + +This means that nested loops can quickly slow the query down if the various +child nodes keep producing many rows. + +## Optimising queries + +With that out of the way, let's see how we can optimise a query. Let's use the +following query as an example: + +```sql +SELECT COUNT(*) +FROM users +WHERE twitter != ''; +``` + +This query simply counts the number of users that have a Twitter profile set. +Let's run this using `EXPLAIN (ANALYZE, BUFFERS)`: + +```sql +EXPLAIN (ANALYZE, BUFFERS) +SELECT COUNT(*) +FROM users +WHERE twitter != ''; +``` + +This will produce the following plan: + +``` +Aggregate (cost=845110.21..845110.22 rows=1 width=8) (actual time=1271.157..1271.158 rows=1 loops=1) + Buffers: shared hit=202662 + -> Seq Scan on users (cost=0.00..844969.99 rows=56087 width=0) (actual time=0.019..1265.883 rows=51833 loops=1) + Filter: ((twitter)::text <> ''::text) + Rows Removed by Filter: 2487813 + Buffers: shared hit=202662 +Planning time: 0.390 ms +Execution time: 1271.180 ms +``` + +From this query plan we can see the following: + +1. We need to perform a sequential scan on the `users` table. +1. This sequential scan filters out 2,487,813 rows using a `Filter`. +1. We use 202,622 buffers, which equals 1.58 GB of memory. +1. It takes us 1.2 seconds to do all of this. + +Considering we are just counting users, that's quite expensive! + +Before we start making any changes, let's see if there are any existing indexes +on the `users` table that we might be able to use. We can obtain this +information by running `\d users` in a `psql` console, then scrolling down to +the `Indexes:` section: + +``` +Indexes: + "users_pkey" PRIMARY KEY, btree (id) + "users_confirmation_token_key" UNIQUE CONSTRAINT, btree (confirmation_token) + "users_email_key" UNIQUE CONSTRAINT, btree (email) + "users_reset_password_token_key" UNIQUE CONSTRAINT, btree (reset_password_token) + "index_on_users_lower_email" btree (lower(email::text)) + "index_on_users_lower_username" btree (lower(username::text)) + "index_on_users_name_lower" btree (lower(name::text)) + "index_users_on_admin" btree (admin) + "index_users_on_created_at" btree (created_at) + "index_users_on_email_trigram" gin (email gin_trgm_ops) + "index_users_on_feed_token" btree (feed_token) + "index_users_on_ghost" btree (ghost) + "index_users_on_incoming_email_token" btree (incoming_email_token) + "index_users_on_name" btree (name) + "index_users_on_name_trigram" gin (name gin_trgm_ops) + "index_users_on_state" btree (state) + "index_users_on_state_and_internal_attrs" btree (state) WHERE ghost <> true AND support_bot <> true + "index_users_on_support_bot" btree (support_bot) + "index_users_on_username" btree (username) + "index_users_on_username_trigram" gin (username gin_trgm_ops) +``` + +Here we can see there is no index on the `twitter` column, which means +PostgreSQL has to perform a sequential scan in this case. Let's try to fix this +by adding the following index: + +```sql +CREATE INDEX CONCURRENTLY twitter_test ON users (twitter); +``` + +If we now re-run our query using `EXPLAIN (ANALYZE, BUFFERS)` we get the +following plan: + +``` +Aggregate (cost=61002.82..61002.83 rows=1 width=8) (actual time=297.311..297.312 rows=1 loops=1) + Buffers: shared hit=51854 dirtied=19 + -> Index Only Scan using twitter_test on users (cost=0.43..60873.13 rows=51877 width=0) (actual time=279.184..293.532 rows=51833 loops=1) + Filter: ((twitter)::text <> ''::text) + Rows Removed by Filter: 2487830 + Heap Fetches: 26037 + Buffers: shared hit=51854 dirtied=19 +Planning time: 0.191 ms +Execution time: 297.334 ms +``` + +Now it takes just under 300 milliseconds to get our data, instead of 1.2 +seconds. However, we still use 51,854 buffers, which is about 400 MB of memory. +300 milliseconds is also quite slow for such a simple query. To understand why +this query is still expensive, let's take a look at the following: + +``` +Index Only Scan using twitter_test on users (cost=0.43..60873.13 rows=51877 width=0) (actual time=279.184..293.532 rows=51833 loops=1) + Filter: ((twitter)::text <> ''::text) + Rows Removed by Filter: 2487830 +``` + +We start with an index only scan on our index, but we somehow still apply a +`Filter` that filters out 2,487,830 rows. Why is that? Well, let's look at how +we created the index: + +```sql +CREATE INDEX CONCURRENTLY twitter_test ON users (twitter); +``` + +We simply told PostgreSQL to index all possible values of the `twitter` column, +even empty strings. Our query in turn uses `WHERE twitter != ''`. This means +that the index does improve things, as we don't need to do a sequential scan, +but we may still encounter empty strings. This means PostgreSQL _has_ to apply a +Filter on the index results to get rid of those values. + +Fortunately, we can improve this even further using "partial indexes". Partial +indexes are indexes with a `WHERE` condition that is applied when indexing data. +For example: + +```sql +CREATE INDEX CONCURRENTLY some_index ON users (email) WHERE id < 100 +``` + +This index would only index the `email` value of rows that match `WHERE id < +100`. We can use partial indexes to change our Twitter index to the following: + +```sql +CREATE INDEX CONCURRENTLY twitter_test ON users (twitter) WHERE twitter != ''; +``` + +Once created, if we run our query again we will be given the following plan: + +``` +Aggregate (cost=1608.26..1608.27 rows=1 width=8) (actual time=19.821..19.821 rows=1 loops=1) + Buffers: shared hit=44036 + -> Index Only Scan using twitter_test on users (cost=0.41..1479.71 rows=51420 width=0) (actual time=0.023..15.514 rows=51833 loops=1) + Heap Fetches: 1208 + Buffers: shared hit=44036 +Planning time: 0.123 ms +Execution time: 19.848 ms +``` + +That's _a lot_ better! Now it only takes 20 milliseconds to get the data, and we +only use about 344 MB of buffers (instead of the original 1.58 GB). The reason +this works is that now PostgreSQL no longer needs to apply a `Filter`, as the +index only contains `twitter` values that are not empty. + +Keep in mind that you shouldn't just add partial indexes every time you want to +optimise a query. Every index has to be updated for every write, and they may +require quite a bit of space, depending on the amount of indexed data. As a +result, first check if there are any existing indexes you may be able to reuse. +If there aren't any, check if you can perhaps slightly change an existing one to +fit both the existing and new queries. Only add a new index if none of the +existing indexes can be used in any way. + +## Queries that can't be optimised + +Now that we have seen how to optimise a query, let's look at another query that +we might not be able to optimise: + +```sql +EXPLAIN (ANALYZE, BUFFERS) +SELECT COUNT(*) +FROM projects +WHERE visibility_level IN (0, 20); +``` + +The output of `EXPLAIN (ANALYZE, BUFFERS)` is as follows: + +``` +Aggregate (cost=922420.60..922420.61 rows=1 width=8) (actual time=3428.535..3428.535 rows=1 loops=1) + Buffers: shared hit=208846 + -> Seq Scan on projects (cost=0.00..908053.18 rows=5746969 width=0) (actual time=0.041..2987.606 rows=5746940 loops=1) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) + Rows Removed by Filter: 65677 + Buffers: shared hit=208846 +Planning time: 2.861 ms +Execution time: 3428.596 ms +``` + +Looking at the output we see the following Filter: + +``` +Filter: (visibility_level = ANY ('{0,20}'::integer[])) +Rows Removed by Filter: 65677 +``` + +Looking at the number of rows removed by the filter, we may be tempted to add an +index on `projects.visibility_level` to somehow turn this Sequential scan + +filter into an index-only scan. + +Unfortunately, doing so is unlikely to improve anything. Contrary to what some +might believe, an index being present _does not guarantee_ that PostgreSQL will +actually use it. For example, when doing a `SELECT * FROM projects` it is much +cheaper to just scan the entire table, instead of using an index and then +fetching data from the table. In such cases PostgreSQL may decide to not use an +index. + +Second, let's think for a moment what our query does: it gets all projects with +visibility level 0 or 20. In the above plan we can see this produces quite a lot +of rows (5,745,940), but how much is that relative to the total? Let's find out +by running the following query: + +```sql +SELECT visibility_level, count(*) AS amount +FROM projects +GROUP BY visibility_level +ORDER BY visibility_level ASC; +``` + +For GitLab.com this produces: + +``` + visibility_level | amount +------------------+--------- + 0 | 5071325 + 10 | 65678 + 20 | 674801 +``` + +Here the total number of projects is 5,811,804, and 5,746,126 of those are of +level 0 or 20. That's 98% of the entire table! + +So no matter what we do, this query will retrieve 98% of the entire table. Since +most time is spent doing exactly that, there isn't really much we can do to +improve this query, other than _not_ running it at all. + +What is important here is that while some may recommend to straight up add an +index the moment you see a sequential scan, it is _much more important_ to first +understand what your query does, how much data it retrieves, and so on. After +all, you can not optimise something you do not understand. + +### Cardinality and selectivity + +Earlier we saw that our query had to retrieve 98% of the rows in the table. +There are two terms commonly used for databases: cardinality, and selectivity. +Cardinality refers to the number of unique values in a particular column in a +table. + +Selectivity is the number of unique values produced by an operation (e.g. an +index scan or filter), relative to the total number of rows. The higher the +selectivity, the more likely PostgreSQL is able to use an index. + +In the above example, there are only 3 unique values: 0, 10, and 20. This means +the cardinality is 3. The selectivity in turn is also very low: 0.0000003% (2 / +5,811,804), because our `Filter` only filters using two values (`0` and `20`). +With such a low selectivity value it's not surprising that PostgreSQL decides +using an index is not worth it, because it would produce almost no unique rows. + +## Rewriting queries + +So the above query can't really be optimised as-is, or at least not much. But +what if we slightly change the purpose of it? What if instead of retrieving all +projects with `visibility_level` 0 or 20, we retrieve those that a user +interacted with somehow? + +Fortunately, GitLab has an answer for this, and it's a table called +`user_interacted_projects`. This table has the following schema: + +``` +Table "public.user_interacted_projects" + Column | Type | Modifiers +------------+---------+----------- + user_id | integer | not null + project_id | integer | not null +Indexes: + "index_user_interacted_projects_on_project_id_and_user_id" UNIQUE, btree (project_id, user_id) + "index_user_interacted_projects_on_user_id" btree (user_id) +Foreign-key constraints: + "fk_rails_0894651f08" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + "fk_rails_722ceba4f7" FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE +``` + +Let's rewrite our query to JOIN this table onto our projects, and get the +projects for a specific user: + +```sql +EXPLAIN ANALYZE +SELECT COUNT(*) +FROM projects +INNER JOIN user_interacted_projects ON user_interacted_projects.project_id = projects.id +WHERE projects.visibility_level IN (0, 20) +AND user_interacted_projects.user_id = 1; +``` + +What we do here is the following: + +1. Get our projects. +1. INNER JOIN `user_interacted_projects`, meaning we're only left with rows in + `projects` that have a corresponding row in `user_interacted_projects`. +1. Limit this to the projects with `visibility_level` of 0 or 20, and to + projects that the user with ID 1 interacted with. + +If we run this query we get the following plan: + +``` + Aggregate (cost=871.03..871.04 rows=1 width=8) (actual time=9.763..9.763 rows=1 loops=1) + -> Nested Loop (cost=0.86..870.52 rows=203 width=0) (actual time=1.072..9.748 rows=143 loops=1) + -> Index Scan using index_user_interacted_projects_on_user_id on user_interacted_projects (cost=0.43..160.71 rows=205 width=4) (actual time=0.939..2.508 rows=145 loops=1) + Index Cond: (user_id = 1) + -> Index Scan using projects_pkey on projects (cost=0.43..3.45 rows=1 width=4) (actual time=0.049..0.050 rows=1 loops=145) + Index Cond: (id = user_interacted_projects.project_id) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) + Rows Removed by Filter: 0 + Planning time: 2.614 ms + Execution time: 9.809 ms +``` + +Here it only took us just under 10 milliseconds to get the data. We can also see +we're retrieving far fewer projects: + +``` +Index Scan using projects_pkey on projects (cost=0.43..3.45 rows=1 width=4) (actual time=0.049..0.050 rows=1 loops=145) + Index Cond: (id = user_interacted_projects.project_id) + Filter: (visibility_level = ANY ('{0,20}'::integer[])) + Rows Removed by Filter: 0 +``` + +Here we see we perform 145 loops (`loops=145`), with every loop producing 1 row +(`rows=1`). This is much less than before, and our query performs much better! + +If we look at the plan we also see our costs are very low: + +``` +Index Scan using projects_pkey on projects (cost=0.43..3.45 rows=1 width=4) (actual time=0.049..0.050 rows=1 loops=145) +``` + +Here our cost is only 3.45, and it only takes us 0.050 milliseconds to do so. +The next index scan is a bit more expensive: + +``` +Index Scan using index_user_interacted_projects_on_user_id on user_interacted_projects (cost=0.43..160.71 rows=205 width=4) (actual time=0.939..2.508 rows=145 loops=1) +``` + +Here the cost is 160.71 (`cost=0.43..160.71`), taking about 2.5 milliseconds +(based on the output of `actual time=....`). + +The most expensive part here is the "Nested Loop" that acts upon the result of +these two index scans: + +``` +Nested Loop (cost=0.86..870.52 rows=203 width=0) (actual time=1.072..9.748 rows=143 loops=1) +``` + +Here we had to perform 870.52 disk page fetches for 203 rows, 9.748 +milliseconds, producing 143 rows in a single loop. + +The key takeaway here is that sometimes you have to rewrite (parts of) a query +to make it better. Sometimes that means having to slightly change your feature +to accommodate for better performance. + +## What makes a bad plan + +This is a bit of a difficult question to answer, because the definition of "bad" +is relative to the problem you are trying to solve. However, some patterns are +best avoided in most cases, such as: + +* Sequential scans on large tables +* Filters that remove a lot of rows +* Performing a certain step (e.g. an index scan) that requires _a lot_ of + buffers (e.g. more than 512 MB for GitLab.com). + +As a general guideline, aim for a query that: + +1. Takes no more than 10 milliseconds. Our target time spent in SQL per request + is around 100 milliseconds, so every query should be as fast as possible. +1. Does not use an excessive number of buffers, relative to the workload. For + example, retrieving ten rows shouldn't require 1 GB of buffers. +1. Does not spend a long amount of time performing disk IO operations. The + setting `track_io_timing` must be enabled for this data to be included in the + output of `EXPLAIN ANALYZE`. +1. Applies a `LIMIT` when retrieving rows without aggregating them, such as + `SELECT * FROM users`. +1. Doesn't use a `Filter` to filter out too many rows, especially if the query + does not use a `LIMIT` to limit the number of returned rows. Filters can + usually be removed by adding a (partial) index. + +These are _guidelines_ and not hard requirements, as different needs may require +different queries. The only _rule_ is that you _must always measure_ your query +(preferably using a production-like database) using `EXPLAIN (ANALYZE, BUFFERS)` +and related tools such as: + +* <https://explain.depesz.com/> +* <http://tatiyants.com/postgres-query-plan-visualization/> + +GitLab employees can also use our chatops solution, available in Slack using the +`/chatops` slash command. You can use chatops to get a query plan by running the +following: + +``` +/chatops run explain SELECT COUNT(*) FROM projects WHERE visibility_level IN (0, 20) +``` + +Visualising the plan using <https://explain.depesz.com/> is also supported: + +``` +/chatops run explain --visual SELECT COUNT(*) FROM projects WHERE visibility_level IN (0, 20) +``` + +Quoting the query is not necessary. + +For more information about the available options, run: + +``` +/chatops run explain --help +``` -- cgit v1.2.1 From 61752ebfa7e1df74f2671e498bceeace88eed4bb Mon Sep 17 00:00:00 2001 From: Stan Hu <stanhu@gmail.com> Date: Fri, 17 Aug 2018 06:14:03 -0700 Subject: Bump Gitaly to 0.117.1 for Rouge update --- GITALY_SERVER_VERSION | 2 +- changelogs/unreleased/sh-bump-gitaly-for-11-2.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/sh-bump-gitaly-for-11-2.yml diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index a38b3bd31b1..90bdef2ea8c 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.117.0 +0.117.1 diff --git a/changelogs/unreleased/sh-bump-gitaly-for-11-2.yml b/changelogs/unreleased/sh-bump-gitaly-for-11-2.yml new file mode 100644 index 00000000000..0e748c3a346 --- /dev/null +++ b/changelogs/unreleased/sh-bump-gitaly-for-11-2.yml @@ -0,0 +1,5 @@ +--- +title: Bump Gitaly to 0.117.1 for Rouge update +merge_request: 21277 +author: +type: security -- cgit v1.2.1 From 4b87d80499eb5c4fc89aee8cadf6c4ee4112dfc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= <alejorro70@gmail.com> Date: Thu, 9 Aug 2018 23:18:49 -0400 Subject: Fix merge requests not showing any diff files for big patches --- GITALY_SERVER_VERSION | 2 +- Gemfile | 2 +- Gemfile.lock | 4 ++-- Gemfile.rails5.lock | 6 +++--- ...ts-does-not-list-all-files-when-one-file-exceeds-size-limits.yml | 5 +++++ lib/gitlab/git/diff.rb | 1 + lib/gitlab/gitaly_client/diff.rb | 2 +- spec/lib/gitlab/gitaly_client/diff_spec.rb | 6 +++++- 8 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/49907-commits-and-merge-requests-does-not-list-all-files-when-one-file-exceeds-size-limits.yml diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 90bdef2ea8c..377d8aca07e 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.117.1 +0.117.2 diff --git a/Gemfile b/Gemfile index 5666e6cebc5..f01adaddd68 100644 --- a/Gemfile +++ b/Gemfile @@ -423,7 +423,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.112.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.113.0', require: 'gitaly' gem 'grpc', '~> 1.11.0' # Locked until https://github.com/google/protobuf/issues/4210 is closed diff --git a/Gemfile.lock b/Gemfile.lock index 1aadc3fd0b6..333e586949f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -284,7 +284,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (0.112.0) + gitaly-proto (0.113.0) google-protobuf (~> 3.1) grpc (~> 1.10) github-linguist (5.3.3) @@ -1045,7 +1045,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 0.112.0) + gitaly-proto (~> 0.113.0) github-linguist (~> 5.3.3) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index af70e2c1939..c919f1a08ae 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -287,7 +287,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (0.112.0) + gitaly-proto (0.113.0) google-protobuf (~> 3.1) grpc (~> 1.10) github-linguist (5.3.3) @@ -1057,7 +1057,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 0.112.0) + gitaly-proto (~> 0.113.0) github-linguist (~> 5.3.3) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) @@ -1216,4 +1216,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.16.2 + 1.16.3 diff --git a/changelogs/unreleased/49907-commits-and-merge-requests-does-not-list-all-files-when-one-file-exceeds-size-limits.yml b/changelogs/unreleased/49907-commits-and-merge-requests-does-not-list-all-files-when-one-file-exceeds-size-limits.yml new file mode 100644 index 00000000000..2fce00a662f --- /dev/null +++ b/changelogs/unreleased/49907-commits-and-merge-requests-does-not-list-all-files-when-one-file-exceeds-size-limits.yml @@ -0,0 +1,5 @@ +--- +title: Fix merge requests not showing any diff files for big patches +merge_request: 21125 +author: +type: fixed diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb index b58296375ef..61ce10ca131 100644 --- a/lib/gitlab/git/diff.rb +++ b/lib/gitlab/git/diff.rb @@ -226,6 +226,7 @@ module Gitlab @new_file = diff.from_id == BLANK_SHA @renamed_file = diff.from_path != diff.to_path @deleted_file = diff.to_id == BLANK_SHA + @too_large = diff.too_large if diff.respond_to?(:too_large) collapse! if diff.respond_to?(:collapsed) && diff.collapsed end diff --git a/lib/gitlab/gitaly_client/diff.rb b/lib/gitlab/gitaly_client/diff.rb index d98a0ce988f..af9d674535b 100644 --- a/lib/gitlab/gitaly_client/diff.rb +++ b/lib/gitlab/gitaly_client/diff.rb @@ -1,7 +1,7 @@ module Gitlab module GitalyClient class Diff - ATTRS = %i(from_path to_path old_mode new_mode from_id to_id patch overflow_marker collapsed).freeze + ATTRS = %i(from_path to_path old_mode new_mode from_id to_id patch overflow_marker collapsed too_large).freeze include AttributesBag end diff --git a/spec/lib/gitlab/gitaly_client/diff_spec.rb b/spec/lib/gitlab/gitaly_client/diff_spec.rb index 00a31ac0b96..ec7ab2fdedb 100644 --- a/spec/lib/gitlab/gitaly_client/diff_spec.rb +++ b/spec/lib/gitlab/gitaly_client/diff_spec.rb @@ -9,7 +9,9 @@ describe Gitlab::GitalyClient::Diff do new_mode: 0100644, from_id: '357406f3075a57708d0163752905cc1576fceacc', to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0', - patch: 'a' * 100 + patch: 'a' * 100, + collapsed: false, + too_large: false } end @@ -22,6 +24,8 @@ describe Gitlab::GitalyClient::Diff do it { is_expected.to respond_to(:from_id) } it { is_expected.to respond_to(:to_id) } it { is_expected.to respond_to(:patch) } + it { is_expected.to respond_to(:collapsed) } + it { is_expected.to respond_to(:too_large) } describe '#==' do it { expect(subject).to eq(described_class.new(diff_fields)) } -- cgit v1.2.1 From 106fdb0b4343d786a179be327bdb8da04d6a60ae Mon Sep 17 00:00:00 2001 From: Ray Paik <rpaik@gitlab.com> Date: Fri, 17 Aug 2018 21:51:19 +0000 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6e1cc9a432..869a9262450 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ You can access a new installation with the login **`root`** and password **`5ive ## Contributing -GitLab is an open source project and we are very happy to accept community contributions. Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details. +GitLab is an open source project and we are very happy to accept community contributions. Please refer to [Contributing to GitLab page](https://about.gitlab.com/contributing/) for more details. ## Licensing -- cgit v1.2.1 From c6d867fb0d382f2acd77318ea622f6b7116d37a9 Mon Sep 17 00:00:00 2001 From: Ray Paik <rpaik@gitlab.com> Date: Fri, 17 Aug 2018 22:16:09 +0000 Subject: Update CONTRIBUTING.md --- CONTRIBUTING.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e68e3b9cab0..fb7c0c88629 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,11 +64,11 @@ As of July 2018, all the documentation for contributing to the GitLab project ha ## Contribute to GitLab -For a first-time step-by-step guide to the contribution process, see -["Contributing to GitLab"](https://about.gitlab.com/contributing/). - Thank you for your interest in contributing to GitLab. This guide details how -to contribute to GitLab in a way that is efficient for everyone. +to contribute to GitLab in a way that is easy for everyone. + +For a first-time step-by-step guide to the contribution process, please see +["Contributing to GitLab"](https://about.gitlab.com/contributing/). Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute). @@ -77,10 +77,10 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial edition. Throughout this guide you will see references to CE and EE for abbreviation. -If you have read this guide and want to know how the GitLab [core team] +If you want to know how the GitLab [core team] operates please see [the GitLab contributing process](PROCESS.md). -- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) +[GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) ## Security vulnerability disclosure -- cgit v1.2.1 From 51dc249f4404fd4e3e2b696cc26b03ac2126880a Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Sat, 18 Aug 2018 00:18:58 -0700 Subject: Fix typo --- spec/lib/gitlab/git/repository_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 35a6fc94753..17348b01006 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -542,7 +542,7 @@ describe Gitlab::Git::Repository, :seed_helper do Gitlab::Shell.new.remove_repository('default', 'my_project') end - shared_examples 'repository mirror fecthing' do + shared_examples 'repository mirror fetching' do it 'fetches a repository as a mirror remote' do subject @@ -569,11 +569,11 @@ describe Gitlab::Git::Repository, :seed_helper do end context 'with gitaly enabled' do - it_behaves_like 'repository mirror fecthing' + it_behaves_like 'repository mirror fetching' end context 'with gitaly enabled', :skip_gitaly_mock do - it_behaves_like 'repository mirror fecthing' + it_behaves_like 'repository mirror fetching' end def new_repository_path -- cgit v1.2.1 From e2118d831f68f844beaf173b6dbb62e7549456c7 Mon Sep 17 00:00:00 2001 From: Maciej Sokolowski <maciej.sokolowski@booking.com> Date: Mon, 20 Aug 2018 12:54:46 +0200 Subject: Test for failure_reason in job webhook --- spec/lib/gitlab/data_builder/build_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb index ee91decafad..14fe196a986 100644 --- a/spec/lib/gitlab/data_builder/build_spec.rb +++ b/spec/lib/gitlab/data_builder/build_spec.rb @@ -15,6 +15,7 @@ describe Gitlab::DataBuilder::Build do it { expect(data[:build_id]).to eq(build.id) } it { expect(data[:build_status]).to eq(build.status) } it { expect(data[:build_allow_failure]).to eq(false) } + it { expect(data[:build_failure_reason]).to eq(build.failure_reason) } it { expect(data[:project_id]).to eq(build.project.id) } it { expect(data[:project_name]).to eq(build.project.full_name) } -- cgit v1.2.1 From e6656ab6801c0a8bd8c22fa15f015e6a2f6cc21f Mon Sep 17 00:00:00 2001 From: Maciej Sokolowski <maciej.sokolowski@booking.com> Date: Mon, 20 Aug 2018 12:59:44 +0200 Subject: Changelog for failure_reason in job webhook --- .../unreleased/47845-propagate_failure_reason-to-job-webhook.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/47845-propagate_failure_reason-to-job-webhook.yml diff --git a/changelogs/unreleased/47845-propagate_failure_reason-to-job-webhook.yml b/changelogs/unreleased/47845-propagate_failure_reason-to-job-webhook.yml new file mode 100644 index 00000000000..3f85f75bef4 --- /dev/null +++ b/changelogs/unreleased/47845-propagate_failure_reason-to-job-webhook.yml @@ -0,0 +1,5 @@ +--- +title: "#47845 Add failure_reason to job webhook" +merge_request: 21143 +author: matemaciek +type: added -- cgit v1.2.1 From 7b2ead6ce85de1b9bd5fb05a01239891a287e094 Mon Sep 17 00:00:00 2001 From: Maciej Sokolowski <maciej.sokolowski@booking.com> Date: Mon, 20 Aug 2018 13:04:22 +0200 Subject: Documentation for failure_reason in job webhook --- doc/user/project/integrations/webhooks.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 77fa517b5b1..770b1810da1 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -1102,6 +1102,7 @@ X-Gitlab-Event: Build Hook "build_finished_at": null, "build_duration": null, "build_allow_failure": false, + "build_failure_reason": "script_failure", "project_id": 380, "project_name": "gitlab-org/gitlab-test", "user": { -- cgit v1.2.1 From feca142fe9fe165b850a4007303edc0e145fe612 Mon Sep 17 00:00:00 2001 From: Dylan Griffith <dyl.griffith@gmail.com> Date: Mon, 20 Aug 2018 12:29:19 +0100 Subject: Disable code_quality check in auto devops QA integration test This test takes too long and leads to flakiness so we disable it for now until somebody can figure out why it takes longer than 20 minutes to finish. --- qa/qa/specs/features/project/auto_devops_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qa/qa/specs/features/project/auto_devops_spec.rb b/qa/qa/specs/features/project/auto_devops_spec.rb index c2c3bef98e4..b3e4889abc0 100644 --- a/qa/qa/specs/features/project/auto_devops_spec.rb +++ b/qa/qa/specs/features/project/auto_devops_spec.rb @@ -15,6 +15,13 @@ module QA p.description = 'Project with Auto Devops' end + # Disable code_quality check in Auto DevOps pipeline as it takes + # too long and times out the test + Factory::Resource::SecretVariable.fabricate! do |resource| + resource.key = 'CODE_QUALITY_DISABLED' + resource.value = '1' + end + # Create Auto Devops compatible repo Factory::Repository::ProjectPush.fabricate! do |push| push.project = project -- cgit v1.2.1 From 6f3c490107f5fa7dd00bce0bbd89e4a0fa4d6389 Mon Sep 17 00:00:00 2001 From: Yorick Peterse <yorickpeterse@gmail.com> Date: Mon, 30 Jul 2018 17:45:49 +0200 Subject: Refactor AutocompleteController This refactors the AutocompleteController according to the guidelines and boundaries discussed in https://gitlab.com/gitlab-org/gitlab-ce/issues/49653. Specifically, ActiveRecord logic is moved to different finders, which are then used in the controller. View logic in turn is moved to presenters, instead of directly using ActiveRecord's "to_json" method. The finder MoveToProjectFinder is also adjusted according to the abstraction guidelines and boundaries, resulting in a much more simple finder. By using finders (and other abstractions) more actively, we can push a lot of logic out of the controller. We also remove the need for various "before_action" hooks, though this could be achieved without using finders as well. The various finders related to AutcompleteController have also been moved into a namespace. This removes the need for calling everything "AutocompleteSmurfFinder", instead you can use "Autocomplete::SmurfFinder". --- app/controllers/autocomplete_controller.rb | 72 ++++------- app/finders/autocomplete/group_finder.rb | 37 ++++++ app/finders/autocomplete/move_to_project_finder.rb | 35 ++++++ app/finders/autocomplete/project_finder.rb | 35 ++++++ app/finders/autocomplete/users_finder.rb | 85 +++++++++++++ app/finders/autocomplete_users_finder.rb | 68 ---------- app/finders/awarded_emoji_finder.rb | 21 ++++ app/finders/move_to_project_finder.rb | 21 ---- app/finders/user_finder.rb | 26 ++++ app/models/award_emoji.rb | 17 +++ app/models/concerns/optionally_search.rb | 19 +++ app/models/project.rb | 21 ++++ app/models/user.rb | 45 ++++++- app/serializers/move_to_project_entity.rb | 6 + app/serializers/move_to_project_serializer.rb | 5 + .../github_import/importer/diff_note_importer.rb | 2 +- .../github_import/importer/issue_importer.rb | 2 +- lib/gitlab/github_import/importer/note_importer.rb | 2 +- .../importer/pull_request_importer.rb | 2 +- spec/controllers/autocomplete_controller_spec.rb | 13 +- spec/finders/autocomplete/group_finder_spec.rb | 58 +++++++++ .../autocomplete/move_to_project_finder_spec.rb | 109 ++++++++++++++++ spec/finders/autocomplete/project_finder_spec.rb | 55 ++++++++ spec/finders/autocomplete/users_finder_spec.rb | 112 +++++++++++++++++ spec/finders/autocomplete_users_finder_spec.rb | 112 ----------------- spec/finders/awarded_emoji_finder_spec.rb | 25 ++++ spec/finders/move_to_project_finder_spec.rb | 97 -------------- spec/finders/user_finder_spec.rb | 43 +++++++ spec/models/award_emoji_spec.rb | 23 ++++ spec/models/concerns/optionally_search_spec.rb | 44 +++++++ spec/models/project_spec.rb | 47 +++++++ spec/models/user_spec.rb | 140 +++++++++++++++++++-- spec/serializers/move_to_project_entity_spec.rb | 19 +++ .../serializers/move_to_project_serializer_spec.rb | 14 +++ 34 files changed, 1061 insertions(+), 371 deletions(-) create mode 100644 app/finders/autocomplete/group_finder.rb create mode 100644 app/finders/autocomplete/move_to_project_finder.rb create mode 100644 app/finders/autocomplete/project_finder.rb create mode 100644 app/finders/autocomplete/users_finder.rb delete mode 100644 app/finders/autocomplete_users_finder.rb create mode 100644 app/finders/awarded_emoji_finder.rb delete mode 100644 app/finders/move_to_project_finder.rb create mode 100644 app/finders/user_finder.rb create mode 100644 app/models/concerns/optionally_search.rb create mode 100644 app/serializers/move_to_project_entity.rb create mode 100644 app/serializers/move_to_project_serializer.rb create mode 100644 spec/finders/autocomplete/group_finder_spec.rb create mode 100644 spec/finders/autocomplete/move_to_project_finder_spec.rb create mode 100644 spec/finders/autocomplete/project_finder_spec.rb create mode 100644 spec/finders/autocomplete/users_finder_spec.rb delete mode 100644 spec/finders/autocomplete_users_finder_spec.rb create mode 100644 spec/finders/awarded_emoji_finder_spec.rb delete mode 100644 spec/finders/move_to_project_finder_spec.rb create mode 100644 spec/finders/user_finder_spec.rb create mode 100644 spec/models/concerns/optionally_search_spec.rb create mode 100644 spec/serializers/move_to_project_entity_spec.rb create mode 100644 spec/serializers/move_to_project_serializer_spec.rb diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 86bade49ec9..9e30b982b06 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -1,67 +1,39 @@ class AutocompleteController < ApplicationController - AWARD_EMOJI_MAX = 100 - skip_before_action :authenticate_user!, only: [:users, :award_emojis] - before_action :load_project, only: [:users] - before_action :load_group, only: [:users] def users - @users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute - - render json: UserSerializer.new.represent(@users) - end - - def user - @user = User.find(params[:id]) - render json: UserSerializer.new.represent(@user) - end - - def projects - project = Project.find_by_id(params[:project_id]) - projects = projects_finder.execute(project, search: params[:search], offset_id: params[:offset_id]) + project = Autocomplete::ProjectFinder + .new(current_user, params) + .execute - render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace) - end + group = Autocomplete::GroupFinder + .new(current_user, project, params) + .execute - def award_emojis - emoji_with_count = AwardEmoji - .limit(AWARD_EMOJI_MAX) - .where(user: current_user) - .group(:name) - .order('count_all DESC, name ASC') - .count + users = Autocomplete::UsersFinder + .new(params: params, current_user: current_user, project: project, group: group) + .execute - # Transform from hash to array to guarantee json order - # e.g. { 'thumbsup' => 2, 'thumbsdown' = 1 } - # => [{ name: 'thumbsup' }, { name: 'thumbsdown' }] - render json: emoji_with_count.map { |k, v| { name: k } } + render json: UserSerializer.new.represent(users) end - private - - def load_group - @group ||= begin - if @project.blank? && params[:group_id].present? - group = Group.find(params[:group_id]) - return render_404 unless can?(current_user, :read_group, group) + def user + user = UserFinder.new(params).execute! - group - end - end + render json: UserSerializer.new.represent(user) end - def load_project - @project ||= begin - if params[:project_id].present? - project = Project.find(params[:project_id]) - return render_404 unless can?(current_user, :read_project, project) + # Displays projects to use for the dropdown when moving a resource from one + # project to another. + def projects + projects = Autocomplete::MoveToProjectFinder + .new(current_user, params) + .execute - project - end - end + render json: MoveToProjectSerializer.new.represent(projects) end - def projects_finder - MoveToProjectFinder.new(current_user) + def award_emojis + render json: AwardedEmojiFinder.new(current_user).execute end end diff --git a/app/finders/autocomplete/group_finder.rb b/app/finders/autocomplete/group_finder.rb new file mode 100644 index 00000000000..dd97ac4c817 --- /dev/null +++ b/app/finders/autocomplete/group_finder.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Autocomplete + # Finder for retrieving a group to use for autocomplete data sources. + class GroupFinder + attr_reader :current_user, :project, :group_id + + # current_user - The currently logged in user, if any. + # project - The Project (if any) to use for the autocomplete data sources. + # params - A Hash containing parameters to use for finding the project. + # + # The following parameters are supported: + # + # * group_id: The ID of the group to find. + def initialize(current_user = nil, project = nil, params = {}) + @current_user = current_user + @project = project + @group_id = params[:group_id] + end + + # Attempts to find a Group based on the current group ID. + def execute + return unless project.blank? && group_id.present? + + group = Group.find(group_id) + + # This removes the need for using `return render_404` and similar patterns + # in controllers that use this finder. + unless Ability.allowed?(current_user, :read_group, group) + raise ActiveRecord::RecordNotFound + .new("Could not find a Group with ID #{group_id}") + end + + group + end + end +end diff --git a/app/finders/autocomplete/move_to_project_finder.rb b/app/finders/autocomplete/move_to_project_finder.rb new file mode 100644 index 00000000000..edaf74c5f92 --- /dev/null +++ b/app/finders/autocomplete/move_to_project_finder.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Autocomplete + # Finder that retrieves a list of projects that an issue can be moved to. + class MoveToProjectFinder + attr_reader :current_user, :search, :project_id, :offset_id + + # current_user - The User object of the user that wants to view the list of + # projects. + # + # params - A Hash containing additional parameters to set. + # + # The following parameters can be set (as Symbols): + # + # * search: An optional search query to apply to the list of projects. + # * project_id: The ID of a project to exclude from the returned relation. + # * offset_id: The ID of a project to use for pagination. When given, only + # projects with a lower ID are included in the list. + def initialize(current_user, params = {}) + @current_user = current_user + @search = params[:search] + @project_id = params[:project_id] + @offset_id = params[:offset_id] + end + + def execute + current_user + .projects_where_can_admin_issues + .optionally_search(search) + .excluding_project(project_id) + .paginate_in_descending_order_using_id(before: offset_id) + .eager_load_namespace_and_owner + end + end +end diff --git a/app/finders/autocomplete/project_finder.rb b/app/finders/autocomplete/project_finder.rb new file mode 100644 index 00000000000..3a4696f4c2e --- /dev/null +++ b/app/finders/autocomplete/project_finder.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Autocomplete + # Finder for retrieving a project to use for autocomplete data sources. + class ProjectFinder + attr_reader :current_user, :project_id + + # current_user - The currently logged in user, if any. + # params - A Hash containing parameters to use for finding the project. + # + # The following parameters are supported: + # + # * project_id: The ID of the project to find. + def initialize(current_user = nil, params = {}) + @current_user = current_user + @project_id = params[:project_id] + end + + # Attempts to find a Project based on the current project ID. + def execute + return if project_id.blank? + + project = Project.find(project_id) + + # This removes the need for using `return render_404` and similar patterns + # in controllers that use this finder. + unless Ability.allowed?(current_user, :read_project, project) + raise ActiveRecord::RecordNotFound + .new("Could not find a Project with ID #{project_id}") + end + + project + end + end +end diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb new file mode 100644 index 00000000000..b2557469079 --- /dev/null +++ b/app/finders/autocomplete/users_finder.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module Autocomplete + class UsersFinder + # The number of users to display in the results is hardcoded to 20, and + # pagination is not supported. This ensures that performance remains + # consistent and removes the need for implementing keyset pagination to + # ensure good performance. + LIMIT = 20 + + attr_reader :current_user, :project, :group, :search, :skip_users, + :author_id, :todo_filter, :todo_state_filter, + :filter_by_current_user + + def initialize(params:, current_user:, project:, group:) + @current_user = current_user + @project = project + @group = group + @search = params[:search] + @skip_users = params[:skip_users] + @author_id = params[:author_id] + @todo_filter = params[:todo_filter] + @todo_state_filter = params[:todo_state_filter] + @filter_by_current_user = params[:current_user] + end + + def execute + items = limited_users + + if search.blank? + # Include current user if available to filter by "Me" + items.unshift(current_user) if prepend_current_user? + + if prepend_author? && (author = User.find_by_id(author_id)) + items.unshift(author) + end + end + + items.uniq + end + + private + + # Returns the users based on the input parameters, as an Array. + # + # This method is separate so it is easier to extend in EE. + def limited_users + # When changing the order of these method calls, make sure that + # reorder_by_name() is called _before_ optionally_search(), otherwise + # reorder_by_name will break the ORDER BY applied in optionally_search(). + find_users + .active + .reorder_by_name + .optionally_search(search) + .where_not_in(skip_users) + .limit_to_todo_authors( + user: current_user, + with_todos: todo_filter, + todo_state: todo_state_filter + ) + .limit(LIMIT) + .to_a + end + + def prepend_current_user? + filter_by_current_user.present? && current_user + end + + def prepend_author? + author_id.present? && current_user + end + + def find_users + if project + project.authorized_users.union_with_user(author_id) + elsif group + group.users_with_parents + elsif current_user + User.all + else + User.none + end + end + end +end diff --git a/app/finders/autocomplete_users_finder.rb b/app/finders/autocomplete_users_finder.rb deleted file mode 100644 index e8a03947f59..00000000000 --- a/app/finders/autocomplete_users_finder.rb +++ /dev/null @@ -1,68 +0,0 @@ -class AutocompleteUsersFinder - # The number of users to display in the results is hardcoded to 20, and - # pagination is not supported. This ensures that performance remains - # consistent and removes the need for implementing keyset pagination to ensure - # good performance. - LIMIT = 20 - - attr_reader :current_user, :project, :group, :search, :skip_users, - :author_id, :params - - def initialize(params:, current_user:, project:, group:) - @current_user = current_user - @project = project - @group = group - @search = params[:search] - @skip_users = params[:skip_users] - @author_id = params[:author_id] - @params = params - end - - def execute - items = find_users - items = items.active - items = items.reorder(:name) - items = items.search(search) if search.present? - items = items.where.not(id: skip_users) if skip_users.present? - items = items.limit(LIMIT) - - if params[:todo_filter].present? && current_user - items = items.todo_authors(current_user.id, params[:todo_state_filter]) - end - - if search.blank? - # Include current user if available to filter by "Me" - if params[:current_user].present? && current_user - items = [current_user, *items].uniq - end - - if author_id.present? && current_user - author = User.find_by_id(author_id) - items = [author, *items].uniq if author - end - end - - items - end - - private - - def find_users - return users_from_project if project - return group.users_with_parents if group - return User.all if current_user - - User.none - end - - def users_from_project - if author_id.present? - union = Gitlab::SQL::Union - .new([project.authorized_users, User.where(id: author_id)]) - - User.from("(#{union.to_sql}) #{User.table_name}") - else - project.authorized_users - end - end -end diff --git a/app/finders/awarded_emoji_finder.rb b/app/finders/awarded_emoji_finder.rb new file mode 100644 index 00000000000..f0cc17f3b26 --- /dev/null +++ b/app/finders/awarded_emoji_finder.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# Class for retrieving information about emoji awarded _by_ a particular user. +class AwardedEmojiFinder + attr_reader :current_user + + # current_user - The User to generate the data for. + def initialize(current_user = nil) + @current_user = current_user + end + + def execute + return [] unless current_user + + # We want the resulting data set to be an Array containing the emoji names + # in descending order, based on how often they were awarded. + AwardEmoji + .award_counts_for_user(current_user) + .map { |name, _| { name: name } } + end +end diff --git a/app/finders/move_to_project_finder.rb b/app/finders/move_to_project_finder.rb deleted file mode 100644 index 038d5565a1e..00000000000 --- a/app/finders/move_to_project_finder.rb +++ /dev/null @@ -1,21 +0,0 @@ -class MoveToProjectFinder - PAGE_SIZE = 50 - - def initialize(user) - @user = user - end - - def execute(from_project, search: nil, offset_id: nil) - projects = @user.projects_where_can_admin_issues - projects = projects.search(search) if search.present? - projects = projects.excluding_project(from_project) - projects = projects.order_id_desc - - # infinite scroll using offset - projects = projects.where('projects.id < ?', offset_id) if offset_id.present? - projects = projects.limit(PAGE_SIZE) - - # to ask for Project#name_with_namespace - projects.includes(namespace: :owner) - end -end diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb new file mode 100644 index 00000000000..484a93c9873 --- /dev/null +++ b/app/finders/user_finder.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# A simple finding for obtaining a single User. +# +# While using `User.find_by` directly is straightforward, it can lead to a lot +# of code duplication. Sometimes we just want to find a user by an ID, other +# times we may want to exclude blocked user. By using this finder (and extending +# it whenever necessary) we can keep this logic in one place. +class UserFinder + attr_reader :params + + def initialize(params) + @params = params + end + + # Tries to find a User, returning nil if none could be found. + def execute + User.find_by(id: params[:id]) + end + + # Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could + # not be found. + def execute! + User.find(params[:id]) + end +end diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb index 99c7866d636..ddc516ccb60 100644 --- a/app/models/award_emoji.rb +++ b/app/models/award_emoji.rb @@ -28,6 +28,23 @@ class AwardEmoji < ActiveRecord::Base .where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids) .group('name', 'awardable_id') end + + # Returns the top 100 emoji awarded by the given user. + # + # The returned value is a Hash mapping emoji names to the number of times + # they were awarded: + # + # { 'thumbsup' => 2, 'thumbsdown' => 1 } + # + # user - The User to get the awards for. + # limt - The maximum number of emoji to return. + def award_counts_for_user(user, limit = 100) + limit(limit) + .where(user: user) + .group(:name) + .order('count_all DESC, name ASC') + .count + end end def downvote? diff --git a/app/models/concerns/optionally_search.rb b/app/models/concerns/optionally_search.rb new file mode 100644 index 00000000000..dec97b7dee8 --- /dev/null +++ b/app/models/concerns/optionally_search.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module OptionallySearch + extend ActiveSupport::Concern + + module ClassMethods + def search(*) + raise( + NotImplementedError, + 'Your model must implement the "search" class method' + ) + end + + # Optionally limits a result set to those matching the given search query. + def optionally_search(query = nil) + query.present? ? search(query) : all + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 94c1d60f071..15336ec2ea2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -28,6 +28,7 @@ class Project < ActiveRecord::Base include WithUploads include BatchDestroyDependentAssociations include FeatureGate + include OptionallySearch extend Gitlab::Cache::RequestCache extend Gitlab::ConfigHelper @@ -384,6 +385,26 @@ class Project < ActiveRecord::Base only_integer: true, message: 'needs to be beetween 10 minutes and 1 month' } + # Paginates a collection using a `WHERE id < ?` condition. + # + # before - A project ID to use for filtering out projects with an equal or + # greater ID. If no ID is given, all projects are included. + # + # limit - The maximum number of rows to include. + def self.paginate_in_descending_order_using_id( + before: nil, + limit: Kaminari.config.default_per_page + ) + relation = order_id_desc.limit(limit) + relation = relation.where('projects.id < ?', before) if before + + relation + end + + def self.eager_load_namespace_and_owner + includes(namespace: :owner) + end + # Returns a collection of projects that is either public or visible to the # logged in user. def self.public_or_visible_to_user(user = nil) diff --git a/app/models/user.rb b/app/models/user.rb index 13b04270a4a..a6ba90794d6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,7 @@ class User < ActiveRecord::Base include BulkMemberAccessLoad include BlocksJsonSerialization include WithUploads + include OptionallySearch DEFAULT_NOTIFICATION_LEVEL = :participating @@ -253,11 +254,41 @@ class User < ActiveRecord::Base scope :external, -> { where(external: true) } scope :active, -> { with_state(:active).non_internal } scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) } - scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } scope :confirmed, -> { where.not(confirmed_at: nil) } + # Limits the users to those that have TODOs, optionally in the given state. + # + # user - The user to get the todos for. + # + # with_todos - If we should limit the result set to users that are the + # authors of todos. + # + # todo_state - An optional state to require the todos to be in. + def self.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil) + if user && with_todos + where(id: Todo.where(user: user, state: todo_state).select(:author_id)) + else + all + end + end + + # Returns a relation that optionally includes the given user. + # + # user_id - The ID of the user to include. + def self.union_with_user(user_id = nil) + if user_id.present? + union = Gitlab::SQL::Union.new([all, User.unscoped.where(id: user_id)]) + + # We use "unscoped" here so that any inner conditions are not repeated for + # the outer query, which would be redundant. + User.unscoped.from("(#{union.to_sql}) #{User.table_name}") + else + all + end + end + def self.with_two_factor_indistinct joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") .where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true) @@ -365,6 +396,18 @@ class User < ActiveRecord::Base ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) end + # Limits the result set to users _not_ in the given query/list of IDs. + # + # users - The list of users to ignore. This can be an + # `ActiveRecord::Relation`, or an Array. + def where_not_in(users = nil) + users ? where.not(id: users) : all + end + + def reorder_by_name + reorder(:name) + end + # searches user by given pattern # it compares name, email, username fields and user's secondary emails with given pattern # This method uses ILIKE on PostgreSQL and LIKE on MySQL. diff --git a/app/serializers/move_to_project_entity.rb b/app/serializers/move_to_project_entity.rb new file mode 100644 index 00000000000..dac1124b0b3 --- /dev/null +++ b/app/serializers/move_to_project_entity.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class MoveToProjectEntity < Grape::Entity + expose :id + expose :name_with_namespace +end diff --git a/app/serializers/move_to_project_serializer.rb b/app/serializers/move_to_project_serializer.rb new file mode 100644 index 00000000000..6a59317505c --- /dev/null +++ b/app/serializers/move_to_project_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class MoveToProjectSerializer < BaseSerializer + entity MoveToProjectEntity +end diff --git a/lib/gitlab/github_import/importer/diff_note_importer.rb b/lib/gitlab/github_import/importer/diff_note_importer.rb index 8274f37d358..d562958e955 100644 --- a/lib/gitlab/github_import/importer/diff_note_importer.rb +++ b/lib/gitlab/github_import/importer/diff_note_importer.rb @@ -13,7 +13,7 @@ module Gitlab @note = note @project = project @client = client - @user_finder = UserFinder.new(project, client) + @user_finder = GithubImport::UserFinder.new(project, client) end def execute diff --git a/lib/gitlab/github_import/importer/issue_importer.rb b/lib/gitlab/github_import/importer/issue_importer.rb index ead4215810f..cb4d7a6a0b6 100644 --- a/lib/gitlab/github_import/importer/issue_importer.rb +++ b/lib/gitlab/github_import/importer/issue_importer.rb @@ -19,7 +19,7 @@ module Gitlab @issue = issue @project = project @client = client - @user_finder = UserFinder.new(project, client) + @user_finder = GithubImport::UserFinder.new(project, client) @milestone_finder = MilestoneFinder.new(project) @issuable_finder = GithubImport::IssuableFinder.new(project, issue) end diff --git a/lib/gitlab/github_import/importer/note_importer.rb b/lib/gitlab/github_import/importer/note_importer.rb index c890f2df360..2b06d1b3baf 100644 --- a/lib/gitlab/github_import/importer/note_importer.rb +++ b/lib/gitlab/github_import/importer/note_importer.rb @@ -13,7 +13,7 @@ module Gitlab @note = note @project = project @client = client - @user_finder = UserFinder.new(project, client) + @user_finder = GithubImport::UserFinder.new(project, client) end def execute diff --git a/lib/gitlab/github_import/importer/pull_request_importer.rb b/lib/gitlab/github_import/importer/pull_request_importer.rb index e4b49d2143a..ed17aa54373 100644 --- a/lib/gitlab/github_import/importer/pull_request_importer.rb +++ b/lib/gitlab/github_import/importer/pull_request_importer.rb @@ -15,7 +15,7 @@ module Gitlab @pull_request = pull_request @project = project @client = client - @user_finder = UserFinder.new(project, client) + @user_finder = GithubImport::UserFinder.new(project, client) @milestone_finder = MilestoneFinder.new(project) @issuable_finder = GithubImport::IssuableFinder.new(project, pull_request) diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 2c59d1929a1..883bb35f396 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -274,14 +274,11 @@ describe AutocompleteController do context 'authorized projects apply limit' do before do - authorized_project2 = create(:project) - authorized_project3 = create(:project) - - authorized_project.add_maintainer(user) - authorized_project2.add_maintainer(user) - authorized_project3.add_maintainer(user) + allow(Kaminari.config).to receive(:default_per_page).and_return(2) - stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 + create_list(:project, 2) do |project| + project.add_maintainer(user) + end end describe 'GET #projects with project ID' do @@ -291,7 +288,7 @@ describe AutocompleteController do it 'returns projects' do expect(json_response).to be_kind_of(Array) - expect(json_response.size).to eq 2 # Of a total of 3 + expect(json_response.size).to eq(Kaminari.config.default_per_page) end end end diff --git a/spec/finders/autocomplete/group_finder_spec.rb b/spec/finders/autocomplete/group_finder_spec.rb new file mode 100644 index 00000000000..d7cb2c3bbe2 --- /dev/null +++ b/spec/finders/autocomplete/group_finder_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Autocomplete::GroupFinder do + let(:user) { create(:user) } + + describe '#execute' do + context 'with a project' do + it 'returns nil' do + project = create(:project) + + expect(described_class.new(user, project).execute).to be_nil + end + end + + context 'without a group ID' do + it 'returns nil' do + expect(described_class.new(user).execute).to be_nil + end + end + + context 'with an empty String as the group ID' do + it 'returns nil' do + expect(described_class.new(user, nil, group_id: '').execute).to be_nil + end + end + + context 'without a project and with a group ID' do + it 'raises ActiveRecord::RecordNotFound if the group does not exist' do + finder = described_class.new(user, nil, group_id: 1) + + expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises ActiveRecord::RecordNotFound if the user can not read the group' do + group = create(:group, :private) + finder = described_class.new(user, nil, group_id: group.id) + + expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises ActiveRecord::RecordNotFound if an anonymous user can not read the group' do + group = create(:group, :private) + finder = described_class.new(nil, nil, group_id: group.id) + + expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'returns the group if it exists and is readable' do + group = create(:group) + finder = described_class.new(user, nil, group_id: group.id) + + expect(finder.execute).to eq(group) + end + end + end +end diff --git a/spec/finders/autocomplete/move_to_project_finder_spec.rb b/spec/finders/autocomplete/move_to_project_finder_spec.rb new file mode 100644 index 00000000000..c3bc410a7f6 --- /dev/null +++ b/spec/finders/autocomplete/move_to_project_finder_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' + +describe Autocomplete::MoveToProjectFinder do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:no_access_project) { create(:project) } + let(:guest_project) { create(:project) } + let(:reporter_project) { create(:project) } + let(:developer_project) { create(:project) } + let(:maintainer_project) { create(:project) } + + describe '#execute' do + context 'filter' do + it 'does not return projects under Gitlab::Access::REPORTER' do + guest_project.add_guest(user) + + finder = described_class.new(user, project_id: project.id) + + expect(finder.execute).to be_empty + end + + it 'returns projects equal or above Gitlab::Access::REPORTER ordered by id in descending order' do + reporter_project.add_reporter(user) + developer_project.add_developer(user) + maintainer_project.add_maintainer(user) + + finder = described_class.new(user, project_id: project.id) + + expect(finder.execute.to_a).to eq([maintainer_project, developer_project, reporter_project]) + end + + it 'does not include the source project' do + project.add_reporter(user) + + finder = described_class.new(user, project_id: project.id) + + expect(finder.execute.to_a).to be_empty + end + + it 'does not return archived projects' do + reporter_project.add_reporter(user) + ::Projects::UpdateService.new(reporter_project, user, archived: true).execute + other_reporter_project = create(:project) + other_reporter_project.add_reporter(user) + + finder = described_class.new(user, project_id: project.id) + + expect(finder.execute.to_a).to eq([other_reporter_project]) + end + + it 'does not return projects for which issues are disabled' do + reporter_project.add_reporter(user) + reporter_project.update(issues_enabled: false) + other_reporter_project = create(:project) + other_reporter_project.add_reporter(user) + + finder = described_class.new(user, project_id: project.id) + + expect(finder.execute.to_a).to eq([other_reporter_project]) + end + + it 'returns a page of projects ordered by id in descending order' do + allow(Kaminari.config).to receive(:default_per_page).and_return(2) + + projects = create_list(:project, 2) do |project| + project.add_developer(user) + end + + finder = described_class.new(user, project_id: project.id) + page = finder.execute.to_a + + expect(page.length).to eq(Kaminari.config.default_per_page) + expect(page[0]).to eq(projects.last) + end + + it 'returns projects after the given offset id' do + reporter_project.add_reporter(user) + developer_project.add_developer(user) + maintainer_project.add_maintainer(user) + + expect(described_class.new(user, project_id: project.id, offset_id: maintainer_project.id).execute.to_a) + .to eq([developer_project, reporter_project]) + + expect(described_class.new(user, project_id: project.id, offset_id: developer_project.id).execute.to_a) + .to eq([reporter_project]) + + expect(described_class.new(user, project_id: project.id, offset_id: reporter_project.id).execute.to_a) + .to be_empty + end + end + + context 'search' do + it 'returns projects matching a search query' do + foo_project = create(:project) + foo_project.add_maintainer(user) + + wadus_project = create(:project, name: 'wadus') + wadus_project.add_maintainer(user) + + expect(described_class.new(user, project_id: project.id).execute.to_a) + .to eq([wadus_project, foo_project]) + + expect(described_class.new(user, project_id: project.id, search: 'wadus').execute.to_a) + .to eq([wadus_project]) + end + end + end +end diff --git a/spec/finders/autocomplete/project_finder_spec.rb b/spec/finders/autocomplete/project_finder_spec.rb new file mode 100644 index 00000000000..207d0598c28 --- /dev/null +++ b/spec/finders/autocomplete/project_finder_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Autocomplete::ProjectFinder do + let(:user) { create(:user) } + + describe '#execute' do + context 'without a project ID' do + it 'returns nil' do + expect(described_class.new(user).execute).to be_nil + end + end + + context 'with an empty String as the project ID' do + it 'returns nil' do + expect(described_class.new(user, project_id: '').execute).to be_nil + end + end + + context 'with a project ID' do + it 'raises ActiveRecord::RecordNotFound if the project does not exist' do + finder = described_class.new(user, project_id: 1) + + expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises ActiveRecord::RecordNotFound if the user can not read the project' do + project = create(:project, :private) + + finder = described_class.new(user, project_id: project.id) + + expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises ActiveRecord::RecordNotFound if an anonymous user can not read the project' do + project = create(:project, :private) + + finder = described_class.new(nil, project_id: project.id) + + expect { finder.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'returns the project if it exists and is readable' do + project = create(:project, :private) + + project.add_maintainer(user) + + finder = described_class.new(user, project_id: project.id) + + expect(finder.execute).to eq(project) + end + end + end +end diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb new file mode 100644 index 00000000000..abd0d6b5185 --- /dev/null +++ b/spec/finders/autocomplete/users_finder_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +describe Autocomplete::UsersFinder do + describe '#execute' do + let!(:user1) { create(:user, username: 'johndoe') } + let!(:user2) { create(:user, :blocked, username: 'notsorandom') } + let!(:external_user) { create(:user, :external) } + let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } + let(:current_user) { create(:user) } + let(:params) { {} } + + let(:project) { nil } + let(:group) { nil } + + subject { described_class.new(params: params, current_user: current_user, project: project, group: group).execute.to_a } + + context 'when current_user not passed or nil' do + let(:current_user) { nil } + + it { is_expected.to match_array([]) } + end + + context 'when project passed' do + let(:project) { create(:project) } + + it { is_expected.to match_array([project.owner]) } + + context 'when author_id passed' do + let(:params) { { author_id: user2.id } } + + it { is_expected.to match_array([project.owner, user2]) } + end + end + + context 'when group passed and project not passed' do + let(:group) { create(:group, :public) } + + before do + group.add_users([user1], GroupMember::DEVELOPER) + end + + it { is_expected.to match_array([user1]) } + end + + context 'when passed a subgroup', :nested_groups do + let(:grandparent) { create(:group, :public) } + let(:parent) { create(:group, :public, parent: grandparent) } + let(:child) { create(:group, :public, parent: parent) } + let(:group) { parent } + + let!(:grandparent_user) { create(:group_member, :developer, group: grandparent).user } + let!(:parent_user) { create(:group_member, :developer, group: parent).user } + let!(:child_user) { create(:group_member, :developer, group: child).user } + + it 'includes users from parent groups as well' do + expect(subject).to match_array([grandparent_user, parent_user]) + end + end + + it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) } + + context 'when filtered by search' do + let(:params) { { search: 'johndoe' } } + + it { is_expected.to match_array([user1]) } + end + + context 'when filtered by skip_users' do + let(:params) { { skip_users: [omniauth_user.id, current_user.id] } } + + it { is_expected.to match_array([user1, external_user]) } + end + + context 'when todos exist' do + let!(:pending_todo1) { create(:todo, user: current_user, author: user1, state: :pending) } + let!(:pending_todo2) { create(:todo, user: external_user, author: omniauth_user, state: :pending) } + let!(:done_todo1) { create(:todo, user: current_user, author: external_user, state: :done) } + let!(:done_todo2) { create(:todo, user: user1, author: external_user, state: :done) } + + context 'when filtered by todo_filter without todo_state_filter' do + let(:params) { { todo_filter: true } } + + it { is_expected.to match_array([]) } + end + + context 'when filtered by todo_filter with pending todo_state_filter' do + let(:params) { { todo_filter: true, todo_state_filter: 'pending' } } + + it { is_expected.to match_array([user1]) } + end + + context 'when filtered by todo_filter with done todo_state_filter' do + let(:params) { { todo_filter: true, todo_state_filter: 'done' } } + + it { is_expected.to match_array([external_user]) } + end + end + + context 'when filtered by current_user' do + let(:current_user) { user2 } + let(:params) { { current_user: true } } + + it { is_expected.to match_array([user2, user1, external_user, omniauth_user]) } + end + + context 'when filtered by author_id' do + let(:params) { { author_id: user2.id } } + + it { is_expected.to match_array([user2, user1, external_user, omniauth_user, current_user]) } + end + end +end diff --git a/spec/finders/autocomplete_users_finder_spec.rb b/spec/finders/autocomplete_users_finder_spec.rb deleted file mode 100644 index dcf9111776e..00000000000 --- a/spec/finders/autocomplete_users_finder_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'spec_helper' - -describe AutocompleteUsersFinder do - describe '#execute' do - let!(:user1) { create(:user, username: 'johndoe') } - let!(:user2) { create(:user, :blocked, username: 'notsorandom') } - let!(:external_user) { create(:user, :external) } - let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') } - let(:current_user) { create(:user) } - let(:params) { {} } - - let(:project) { nil } - let(:group) { nil } - - subject { described_class.new(params: params, current_user: current_user, project: project, group: group).execute.to_a } - - context 'when current_user not passed or nil' do - let(:current_user) { nil } - - it { is_expected.to match_array([]) } - end - - context 'when project passed' do - let(:project) { create(:project) } - - it { is_expected.to match_array([project.owner]) } - - context 'when author_id passed' do - let(:params) { { author_id: user2.id } } - - it { is_expected.to match_array([project.owner, user2]) } - end - end - - context 'when group passed and project not passed' do - let(:group) { create(:group, :public) } - - before do - group.add_users([user1], GroupMember::DEVELOPER) - end - - it { is_expected.to match_array([user1]) } - end - - context 'when passed a subgroup', :nested_groups do - let(:grandparent) { create(:group, :public) } - let(:parent) { create(:group, :public, parent: grandparent) } - let(:child) { create(:group, :public, parent: parent) } - let(:group) { parent } - - let!(:grandparent_user) { create(:group_member, :developer, group: grandparent).user } - let!(:parent_user) { create(:group_member, :developer, group: parent).user } - let!(:child_user) { create(:group_member, :developer, group: child).user } - - it 'includes users from parent groups as well' do - expect(subject).to match_array([grandparent_user, parent_user]) - end - end - - it { is_expected.to match_array([user1, external_user, omniauth_user, current_user]) } - - context 'when filtered by search' do - let(:params) { { search: 'johndoe' } } - - it { is_expected.to match_array([user1]) } - end - - context 'when filtered by skip_users' do - let(:params) { { skip_users: [omniauth_user.id, current_user.id] } } - - it { is_expected.to match_array([user1, external_user]) } - end - - context 'when todos exist' do - let!(:pending_todo1) { create(:todo, user: current_user, author: user1, state: :pending) } - let!(:pending_todo2) { create(:todo, user: external_user, author: omniauth_user, state: :pending) } - let!(:done_todo1) { create(:todo, user: current_user, author: external_user, state: :done) } - let!(:done_todo2) { create(:todo, user: user1, author: external_user, state: :done) } - - context 'when filtered by todo_filter without todo_state_filter' do - let(:params) { { todo_filter: true } } - - it { is_expected.to match_array([]) } - end - - context 'when filtered by todo_filter with pending todo_state_filter' do - let(:params) { { todo_filter: true, todo_state_filter: 'pending' } } - - it { is_expected.to match_array([user1]) } - end - - context 'when filtered by todo_filter with done todo_state_filter' do - let(:params) { { todo_filter: true, todo_state_filter: 'done' } } - - it { is_expected.to match_array([external_user]) } - end - end - - context 'when filtered by current_user' do - let(:current_user) { user2 } - let(:params) { { current_user: true } } - - it { is_expected.to match_array([user2, user1, external_user, omniauth_user]) } - end - - context 'when filtered by author_id' do - let(:params) { { author_id: user2.id } } - - it { is_expected.to match_array([user2, user1, external_user, omniauth_user, current_user]) } - end - end -end diff --git a/spec/finders/awarded_emoji_finder_spec.rb b/spec/finders/awarded_emoji_finder_spec.rb new file mode 100644 index 00000000000..d4479df7418 --- /dev/null +++ b/spec/finders/awarded_emoji_finder_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AwardedEmojiFinder do + describe '#execute' do + it 'returns an Array containing the awarded emoji names' do + user = create(:user) + + create(:award_emoji, user: user, name: 'thumbsup') + create(:award_emoji, user: user, name: 'thumbsup') + create(:award_emoji, user: user, name: 'thumbsdown') + + awarded = described_class.new(user).execute + + expect(awarded).to eq([{ name: 'thumbsup' }, { name: 'thumbsdown' }]) + end + + it 'returns an empty Array when no user is given' do + awarded = described_class.new.execute + + expect(awarded).to be_empty + end + end +end diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb deleted file mode 100644 index 1b3f44cced1..00000000000 --- a/spec/finders/move_to_project_finder_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'spec_helper' - -describe MoveToProjectFinder do - let(:user) { create(:user) } - let(:project) { create(:project) } - - let(:no_access_project) { create(:project) } - let(:guest_project) { create(:project) } - let(:reporter_project) { create(:project) } - let(:developer_project) { create(:project) } - let(:maintainer_project) { create(:project) } - - subject { described_class.new(user) } - - describe '#execute' do - context 'filter' do - it 'does not return projects under Gitlab::Access::REPORTER' do - guest_project.add_guest(user) - - expect(subject.execute(project)).to be_empty - end - - it 'returns projects equal or above Gitlab::Access::REPORTER ordered by id in descending order' do - reporter_project.add_reporter(user) - developer_project.add_developer(user) - maintainer_project.add_maintainer(user) - - expect(subject.execute(project).to_a).to eq([maintainer_project, developer_project, reporter_project]) - end - - it 'does not include the source project' do - project.add_reporter(user) - - expect(subject.execute(project).to_a).to be_empty - end - - it 'does not return archived projects' do - reporter_project.add_reporter(user) - ::Projects::UpdateService.new(reporter_project, user, archived: true).execute - other_reporter_project = create(:project) - other_reporter_project.add_reporter(user) - - expect(subject.execute(project).to_a).to eq([other_reporter_project]) - end - - it 'does not return projects for which issues are disabled' do - reporter_project.add_reporter(user) - reporter_project.update(issues_enabled: false) - other_reporter_project = create(:project) - other_reporter_project.add_reporter(user) - - expect(subject.execute(project).to_a).to eq([other_reporter_project]) - end - - it 'returns a page of projects ordered by id in descending order' do - stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 - - reporter_project.add_reporter(user) - developer_project.add_developer(user) - maintainer_project.add_maintainer(user) - - expect(subject.execute(project).to_a).to eq([maintainer_project, developer_project]) - end - - it 'returns projects after the given offset id' do - stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 - - reporter_project.add_reporter(user) - developer_project.add_developer(user) - maintainer_project.add_maintainer(user) - - expect(subject.execute(project, search: nil, offset_id: maintainer_project.id).to_a).to eq([developer_project, reporter_project]) - expect(subject.execute(project, search: nil, offset_id: developer_project.id).to_a).to eq([reporter_project]) - expect(subject.execute(project, search: nil, offset_id: reporter_project.id).to_a).to be_empty - end - end - - context 'search' do - it 'uses Project#search' do - expect(user).to receive_message_chain(:projects_where_can_admin_issues, :search) { Project.all } - - subject.execute(project, search: 'wadus') - end - - it 'returns projects matching a search query' do - foo_project = create(:project) - foo_project.add_maintainer(user) - - wadus_project = create(:project, name: 'wadus') - wadus_project.add_maintainer(user) - - expect(subject.execute(project).to_a).to eq([wadus_project, foo_project]) - expect(subject.execute(project, search: 'wadus').to_a).to eq([wadus_project]) - end - end - end -end diff --git a/spec/finders/user_finder_spec.rb b/spec/finders/user_finder_spec.rb new file mode 100644 index 00000000000..e53aa50dd33 --- /dev/null +++ b/spec/finders/user_finder_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UserFinder do + describe '#execute' do + context 'when the user exists' do + it 'returns the user' do + user = create(:user) + found = described_class.new(id: user.id).execute + + expect(found).to eq(user) + end + end + + context 'when the user does not exist' do + it 'returns nil' do + found = described_class.new(id: 1).execute + + expect(found).to be_nil + end + end + end + + describe '#execute!' do + context 'when the user exists' do + it 'returns the user' do + user = create(:user) + found = described_class.new(id: user.id).execute! + + expect(found).to eq(user) + end + end + + context 'when the user does not exist' do + it 'raises ActiveRecord::RecordNotFound' do + finder = described_class.new(id: 1) + + expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb index b909e04dfc3..3f52091698c 100644 --- a/spec/models/award_emoji_spec.rb +++ b/spec/models/award_emoji_spec.rb @@ -77,4 +77,27 @@ describe AwardEmoji do end end end + + describe '.award_counts_for_user' do + let(:user) { create(:user) } + + before do + create(:award_emoji, user: user, name: 'thumbsup') + create(:award_emoji, user: user, name: 'thumbsup') + create(:award_emoji, user: user, name: 'thumbsdown') + create(:award_emoji, user: user, name: '+1') + end + + it 'returns the awarded emoji in descending order' do + awards = described_class.award_counts_for_user(user) + + expect(awards).to eq('thumbsup' => 2, 'thumbsdown' => 1, '+1' => 1) + end + + it 'limits the returned number of rows' do + awards = described_class.award_counts_for_user(user, 1) + + expect(awards).to eq('thumbsup' => 2) + end + end end diff --git a/spec/models/concerns/optionally_search_spec.rb b/spec/models/concerns/optionally_search_spec.rb new file mode 100644 index 00000000000..ff4212ddf18 --- /dev/null +++ b/spec/models/concerns/optionally_search_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe OptionallySearch do + let(:model) do + Class.new(ActiveRecord::Base) do + self.table_name = 'users' + + include OptionallySearch + end + end + + describe '.search' do + it 'raises NotImplementedError' do + expect { model.search('foo') }.to raise_error(NotImplementedError) + end + end + + describe '.optionally_search' do + context 'when a query is given' do + it 'delegates to the search method' do + expect(model) + .to receive(:search) + .with('foo') + + model.optionally_search('foo') + end + end + + context 'when no query is given' do + it 'returns the current relation' do + expect(model.optionally_search).to be_a_kind_of(ActiveRecord::Relation) + end + end + + context 'when an empty query is given' do + it 'returns the current relation' do + expect(model.optionally_search('')) + .to be_a_kind_of(ActiveRecord::Relation) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d8a5e5f6869..56c07f5793b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1478,6 +1478,53 @@ describe Project do end end + describe '.optionally_search' do + let(:project) { create(:project) } + + it 'searches for projects matching the query if one is given' do + relation = described_class.optionally_search(project.name) + + expect(relation).to eq([project]) + end + + it 'returns the current relation if no search query is given' do + relation = described_class.where(id: project.id) + + expect(relation.optionally_search).to eq(relation) + end + end + + describe '.paginate_in_descending_order_using_id' do + let!(:project1) { create(:project) } + let!(:project2) { create(:project) } + + it 'orders the relation in descending order' do + expect(described_class.paginate_in_descending_order_using_id) + .to eq([project2, project1]) + end + + it 'applies a limit to the relation' do + expect(described_class.paginate_in_descending_order_using_id(limit: 1)) + .to eq([project2]) + end + + it 'limits projects by and ID when given' do + expect(described_class.paginate_in_descending_order_using_id(before: project2.id)) + .to eq([project1]) + end + end + + describe '.including_namespace_and_owner' do + it 'eager loads the namespace and namespace owner' do + create(:project) + + row = described_class.eager_load_namespace_and_owner.to_a.first + recorder = ActiveRecord::QueryRecorder.new { row.namespace.owner } + + expect(recorder.count).to be_zero + end + end + describe '#expire_caches_before_rename' do let(:project) { create(:project, :repository) } let(:repo) { double(:repo, exists?: true) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f5e2c977104..9763477a711 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -346,17 +346,55 @@ describe User do end end - describe '.todo_authors' do - it 'filters users' do - create :user - user_2 = create :user - user_3 = create :user - current_user = create :user - create(:todo, user: current_user, author: user_2, state: :done) - create(:todo, user: current_user, author: user_3, state: :pending) + describe '.limit_to_todo_authors' do + context 'when filtering by todo authors' do + let(:user1) { create(:user) } + let(:user2) { create(:user) } - expect(described_class.todo_authors(current_user.id, 'pending')).to eq [user_3] - expect(described_class.todo_authors(current_user.id, 'done')).to eq [user_2] + before do + create(:todo, user: user1, author: user1, state: :done) + create(:todo, user: user2, author: user2, state: :pending) + end + + it 'only returns users that have authored todos' do + users = described_class.limit_to_todo_authors( + user: user2, + with_todos: true, + todo_state: :pending + ) + + expect(users).to eq([user2]) + end + + it 'ignores users that do not have a todo in the matching state' do + users = described_class.limit_to_todo_authors( + user: user1, + with_todos: true, + todo_state: :pending + ) + + expect(users).to be_empty + end + end + + context 'when not filtering by todo authors' do + it 'returns the input relation' do + user1 = create(:user) + user2 = create(:user) + rel = described_class.limit_to_todo_authors(user: user1) + + expect(rel).to include(user1, user2) + end + end + + context 'when no user is provided' do + it 'returns the input relation' do + user1 = create(:user) + user2 = create(:user) + rel = described_class.limit_to_todo_authors + + expect(rel).to include(user1, user2) + end end end end @@ -2901,4 +2939,86 @@ describe User do let(:uploader_class) { AttachmentUploader } end end + + describe '.union_with_user' do + context 'when no user ID is provided' do + it 'returns the input relation' do + user = create(:user) + + expect(described_class.union_with_user).to eq([user]) + end + end + + context 'when a user ID is provided' do + it 'includes the user object in the returned relation' do + user1 = create(:user) + user2 = create(:user) + users = described_class.where(id: user1.id).union_with_user(user2.id) + + expect(users).to include(user1) + expect(users).to include(user2) + end + + it 'does not re-apply any WHERE conditions on the outer query' do + relation = described_class.where(id: 1).union_with_user(2) + + expect(relation.arel.where_sql).to be_nil + end + end + end + + describe '.optionally_search' do + context 'using nil as the argument' do + it 'returns the current relation' do + user = create(:user) + + expect(described_class.optionally_search).to eq([user]) + end + end + + context 'using an empty String as the argument' do + it 'returns the current relation' do + user = create(:user) + + expect(described_class.optionally_search('')).to eq([user]) + end + end + + context 'using a non-empty String' do + it 'returns users matching the search query' do + user1 = create(:user) + create(:user) + + expect(described_class.optionally_search(user1.name)).to eq([user1]) + end + end + end + + describe '.where_not_in' do + context 'without an argument' do + it 'returns the current relation' do + user = create(:user) + + expect(described_class.where_not_in).to eq([user]) + end + end + + context 'using a list of user IDs' do + it 'excludes the users from the returned relation' do + user1 = create(:user) + user2 = create(:user) + + expect(described_class.where_not_in([user2.id])).to eq([user1]) + end + end + end + + describe '.reorder_by_name' do + it 'reorders the input relation' do + user1 = create(:user, name: 'A') + user2 = create(:user, name: 'B') + + expect(described_class.reorder_by_name).to eq([user1, user2]) + end + end end diff --git a/spec/serializers/move_to_project_entity_spec.rb b/spec/serializers/move_to_project_entity_spec.rb new file mode 100644 index 00000000000..ac495eadb68 --- /dev/null +++ b/spec/serializers/move_to_project_entity_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MoveToProjectEntity do + describe '#as_json' do + let(:project) { build(:project, id: 1) } + + subject { described_class.new(project).as_json } + + it 'includes the project ID' do + expect(subject[:id]).to eq(project.id) + end + + it 'includes the full path' do + expect(subject[:name_with_namespace]).to eq(project.name_with_namespace) + end + end +end diff --git a/spec/serializers/move_to_project_serializer_spec.rb b/spec/serializers/move_to_project_serializer_spec.rb new file mode 100644 index 00000000000..841ac969eeb --- /dev/null +++ b/spec/serializers/move_to_project_serializer_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MoveToProjectSerializer do + describe '#represent' do + it 'includes the name and name with namespace' do + project = build(:project, id: 1) + output = described_class.new.represent(project) + + expect(output).to include(:id, :name_with_namespace) + end + end +end -- cgit v1.2.1 From 67e0082ecee50040103928d600b264078046f63a Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller <bbodenmiller@hotmail.com> Date: Mon, 20 Aug 2018 14:58:08 +0000 Subject: clarify runners currently online text --- app/views/admin/runners/index.html.haml | 2 +- changelogs/unreleased/runners-online.yml | 5 +++++ spec/features/admin/admin_runners_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/runners-online.yml diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 8dfd176f1b7..9280ff4d478 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -49,7 +49,7 @@ = submit_tag 'Search', class: 'btn' .float-right.light - Runners with last contact more than a minute ago: #{@active_runners_cnt} + Runners currently online: #{@active_runners_cnt} %br diff --git a/changelogs/unreleased/runners-online.yml b/changelogs/unreleased/runners-online.yml new file mode 100644 index 00000000000..a732d9cb723 --- /dev/null +++ b/changelogs/unreleased/runners-online.yml @@ -0,0 +1,5 @@ +--- +title: Clarify current runners online text +merge_request: 21151 +author: Ben Bodenmiller +type: other diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index be8754a5315..5623e47eadf 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -20,7 +20,7 @@ describe "Admin Runners" do it 'has all necessary texts' do expect(page).to have_text "Setup a shared Runner manually" - expect(page).to have_text "Runners with last contact more than a minute ago: 1" + expect(page).to have_text "Runners currently online: 1" end describe 'search' do @@ -55,7 +55,7 @@ describe "Admin Runners" do it 'has all necessary texts including no runner message' do expect(page).to have_text "Setup a shared Runner manually" - expect(page).to have_text "Runners with last contact more than a minute ago: 0" + expect(page).to have_text "Runners currently online: 0" expect(page).to have_text 'No runners found' end end -- cgit v1.2.1 From 4d7d4a0c16bfcac87416a0ba51296039e4c8b1d4 Mon Sep 17 00:00:00 2001 From: Gilbert Roulot <groulot@gitlab.com> Date: Mon, 20 Aug 2018 18:09:57 +0000 Subject: Remove Gemnasium service --- Gemfile | 3 - Gemfile.lock | 3 - Gemfile.rails5.lock | 3 - app/models/project.rb | 1 - app/models/project_services/gemnasium_service.rb | 62 ------------------ .../unreleased/6010_remove_gemnasium_service.yml | 5 ++ doc/api/services.md | 42 ------------ doc/integration/README.md | 2 +- doc/user/project/integrations/project_services.md | 1 - lib/api/services.rb | 15 ----- spec/lib/gitlab/import_export/all_models.yml | 1 - .../project_services/gemnasium_service_spec.rb | 74 ---------------------- spec/models/project_spec.rb | 1 - vendor/licenses.csv | 1 - 14 files changed, 6 insertions(+), 208 deletions(-) delete mode 100644 app/models/project_services/gemnasium_service.rb create mode 100644 changelogs/unreleased/6010_remove_gemnasium_service.yml delete mode 100644 spec/models/project_services/gemnasium_service_spec.rb diff --git a/Gemfile b/Gemfile index f01adaddd68..bcede83df12 100644 --- a/Gemfile +++ b/Gemfile @@ -214,9 +214,6 @@ gem 'jira-ruby', '~> 1.4' # Flowdock integration gem 'gitlab-flowdock-git-hook', '~> 1.0.1' -# Gemnasium integration -gem 'gemnasium-gitlab-service', '~> 0.2' - # Slack integration gem 'slack-notifier', '~> 1.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index 333e586949f..15a105579fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -269,8 +269,6 @@ GEM fuubar (2.2.0) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - gemnasium-gitlab-service (0.2.6) - rugged (~> 0.21) gemojione (3.3.0) json get_process_mem (0.2.0) @@ -1040,7 +1038,6 @@ DEPENDENCIES font-awesome-rails (~> 4.7) foreman (~> 0.84.0) fuubar (~> 2.2.0) - gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.3) gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index c919f1a08ae..7803d12c6b4 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -272,8 +272,6 @@ GEM fuubar (2.2.0) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - gemnasium-gitlab-service (0.2.6) - rugged (~> 0.21) gemojione (3.3.0) json get_process_mem (0.2.0) @@ -1052,7 +1050,6 @@ DEPENDENCIES font-awesome-rails (~> 4.7) foreman (~> 0.84.0) fuubar (~> 2.2.0) - gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.3) gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) diff --git a/app/models/project.rb b/app/models/project.rb index 15336ec2ea2..8f631d7f0ed 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -141,7 +141,6 @@ class Project < ActiveRecord::Base has_one :flowdock_service has_one :assembla_service has_one :asana_service - has_one :gemnasium_service has_one :mattermost_slash_commands_service has_one :mattermost_service has_one :slack_slash_commands_service diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb deleted file mode 100644 index 67a92c441b1..00000000000 --- a/app/models/project_services/gemnasium_service.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require "gemnasium/gitlab_service" - -class GemnasiumService < Service - prop_accessor :token, :api_key - validates :token, :api_key, presence: true, if: :activated? - validate :deprecation_validation - - def title - 'Gemnasium' - end - - def description - 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.' - end - - def self.to_param - 'gemnasium' - end - - def fields - [ - { type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ', required: true }, - { type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com', required: true } - ] - end - - def self.supported_events - %w(push) - end - - def deprecated? - true - end - - def deprecation_message - "Gemnasium has been acquired by GitLab in January 2018. Since May 15, 2018, the service provided by Gemnasium is no longer available." - end - - def deprecation_validation - errors[:base] << deprecation_message - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - # Gitaly: this class will be removed https://gitlab.com/gitlab-org/gitlab-ee/issues/6010 - repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - project.repository.path_to_repo - end - - Gemnasium::GitlabService.execute( - ref: data[:ref], - before: data[:before], - after: data[:after], - token: token, - api_key: api_key, - repo: repo_path - ) - end -end diff --git a/changelogs/unreleased/6010_remove_gemnasium_service.yml b/changelogs/unreleased/6010_remove_gemnasium_service.yml new file mode 100644 index 00000000000..900e84c9eed --- /dev/null +++ b/changelogs/unreleased/6010_remove_gemnasium_service.yml @@ -0,0 +1,5 @@ +--- +title: Remove Gemnasium service +merge_request: 21185 +author: +type: removed diff --git a/doc/api/services.md b/doc/api/services.md index efa173180bb..8c59b232b6d 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -401,48 +401,6 @@ Get Flowdock service settings for a project. GET /projects/:id/services/flowdock ``` -## Gemnasium - -Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities. - -CAUTION: **Warning:** -Gemnasium service integration has been deprecated in GitLab 11.0. Gemnasium has been -[acquired by GitLab](https://about.gitlab.com/press/releases/2018-01-30-gemnasium-acquisition.html) -in January 2018 and since May 15, 2018, the service provided by Gemnasium is no longer available. -You can [migrate from Gemnasium to GitLab](https://docs.gitlab.com/ee/user/project/import/gemnasium.html) -to keep monitoring your dependencies. - -### Create/Edit Gemnasium service - -Set Gemnasium service for a project. - -``` -PUT /projects/:id/services/gemnasium -``` - -Parameters: - -| Parameter | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `api_key` | string | true | Your personal API KEY on gemnasium.com | -| `token` | string | true | The project's slug on gemnasium.com | - -### Delete Gemnasium service - -Delete Gemnasium service for a project. - -``` -DELETE /projects/:id/services/gemnasium -``` - -### Get Gemnasium service settings - -Get Gemnasium service settings for a project. - -``` -GET /projects/:id/services/gemnasium -``` - ## Hangouts Chat Google GSuite team collaboration tool. diff --git a/doc/integration/README.md b/doc/integration/README.md index 54e78bdef54..8a93d4cb84b 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -30,7 +30,7 @@ Bitbucket.org account ## Project services -Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, +Integration with services such as Campfire, Flowdock, HipChat, Pivotal Tracker, and Slack are available in the form of a [Project Service][]. [Project Service]: ../user/project/integrations/project_services.md diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md index 05ee1b4e6d7..efb0381d7aa 100644 --- a/doc/user/project/integrations/project_services.md +++ b/doc/user/project/integrations/project_services.md @@ -34,7 +34,6 @@ Click on the service links to see further configuration instructions and details | [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | | External Wiki | Replaces the link to the internal wiki with a link to an external wiki | | Flowdock | Flowdock is a collaboration web app for technical teams | -| Gemnasium _(Has been deprecated in GitLab 11.0)_ | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities | | [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat | | [HipChat](hipchat.md) | Private group chat and IM | | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | diff --git a/lib/api/services.rb b/lib/api/services.rb index 1f2bf546cd7..d1a5ee7db35 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -354,20 +354,6 @@ module API desc: 'Flowdock token' } ], - 'gemnasium' => [ - { - required: true, - name: :api_key, - type: String, - desc: 'Your personal API key on gemnasium.com' - }, - { - required: true, - name: :token, - type: String, - desc: "The project's slug on gemnasium.com" - } - ], 'hangouts-chat' => [ { required: true, @@ -695,7 +681,6 @@ module API EmailsOnPushService, ExternalWikiService, FlowdockService, - GemnasiumService, HangoutsChatService, HipchatService, IrkerService, diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index e9a1932407d..b4269bd5786 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -210,7 +210,6 @@ project: - flowdock_service - assembla_service - asana_service -- gemnasium_service - slack_service - microsoft_teams_service - mattermost_service diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb deleted file mode 100644 index 6e417323e40..00000000000 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'spec_helper' - -describe GemnasiumService do - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - - describe 'Validations' do - context 'when service is active' do - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:token) } - it { is_expected.to validate_presence_of(:api_key) } - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:token) } - it { is_expected.not_to validate_presence_of(:api_key) } - end - end - - describe "deprecated?" do - let(:project) { create(:project, :repository) } - let(:gemnasium_service) { described_class.new } - - before do - allow(gemnasium_service).to receive_messages( - project_id: project.id, - project: project, - service_hook: true, - token: 'verySecret', - api_key: 'GemnasiumUserApiKey' - ) - end - - it "is true" do - expect(gemnasium_service.deprecated?).to be true - end - - it "can't create a new service" do - expect(gemnasium_service.save).to be false - expect(gemnasium_service.errors[:base].first) - .to eq('Gemnasium has been acquired by GitLab in January 2018. Since May 15, 2018, the service provided by Gemnasium is no longer available.') - end - end - - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:gemnasium_service) { described_class.new } - let(:sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } - - before do - allow(gemnasium_service).to receive_messages( - project_id: project.id, - project: project, - service_hook: true, - token: 'verySecret', - api_key: 'GemnasiumUserApiKey' - ) - end - it "calls Gemnasium service" do - expect(Gemnasium::GitlabService).to receive(:execute).with(an_instance_of(Hash)).once - gemnasium_service.execute(sample_data) - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 56c07f5793b..8cb706b54b0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -42,7 +42,6 @@ describe Project do it { is_expected.to have_one(:assembla_service) } it { is_expected.to have_one(:slack_slash_commands_service) } it { is_expected.to have_one(:mattermost_slash_commands_service) } - it { is_expected.to have_one(:gemnasium_service) } it { is_expected.to have_one(:buildkite_service) } it { is_expected.to have_one(:bamboo_service) } it { is_expected.to have_one(:teamcity_service) } diff --git a/vendor/licenses.csv b/vendor/licenses.csv index a462daf3067..ffe56286684 100644 --- a/vendor/licenses.csv +++ b/vendor/licenses.csv @@ -615,7 +615,6 @@ function-bind,1.1.1,MIT functional-red-black-tree,1.0.1,MIT fuzzaldrin-plus,0.5.0,MIT gauge,2.7.4,ISC -gemnasium-gitlab-service,0.2.6,MIT gemojione,3.3.0,MIT generate-function,2.0.0,MIT generate-object-property,1.2.0,MIT -- cgit v1.2.1 From d67936b68f2ba872f5335be1f672f862410c03e5 Mon Sep 17 00:00:00 2001 From: Dan Davison <ddavison@gitlab.com> Date: Mon, 20 Aug 2018 18:13:54 +0000 Subject: add initial smoke tests and documentation --- doc/development/testing_guide/smoke.md | 16 ++++++++ doc/development/testing_guide/testing_levels.md | 8 ++++ qa/README.md | 12 +++--- qa/qa.rb | 6 ++- qa/qa/scenario/taggable.rb | 17 -------- qa/qa/scenario/template.rb | 33 +++++++++++++--- qa/qa/scenario/test/instance.rb | 36 ----------------- qa/qa/scenario/test/instance/all.rb | 15 ++++++++ qa/qa/scenario/test/instance/smoke.rb | 17 ++++++++ qa/qa/specs/features/api/basics_spec.rb | 2 +- qa/qa/specs/features/api/users_spec.rb | 2 +- qa/qa/specs/features/login/basic_spec.rb | 15 ++++++++ qa/qa/specs/features/login/ldap_spec.rb | 2 +- .../specs/features/mattermost/group_create_spec.rb | 2 +- qa/qa/specs/features/mattermost/login_spec.rb | 2 +- qa/qa/specs/features/merge_request/create_spec.rb | 23 ++++++++++- qa/qa/specs/features/merge_request/rebase_spec.rb | 2 +- qa/qa/specs/features/merge_request/squash_spec.rb | 2 +- qa/qa/specs/features/project/activity_spec.rb | 2 +- .../specs/features/project/add_deploy_key_spec.rb | 2 +- .../features/project/add_secret_variable_spec.rb | 2 +- qa/qa/specs/features/project/auto_devops_spec.rb | 2 +- qa/qa/specs/features/project/create_issue_spec.rb | 2 +- qa/qa/specs/features/project/create_spec.rb | 2 +- .../features/project/deploy_key_clone_spec.rb | 2 +- qa/qa/specs/features/project/fork_project_spec.rb | 2 +- .../features/project/import_from_github_spec.rb | 2 +- qa/qa/specs/features/project/pipelines_spec.rb | 2 +- qa/qa/specs/features/project/wikis_spec.rb | 2 +- qa/qa/specs/features/repository/clone_spec.rb | 2 +- .../features/repository/protected_branches_spec.rb | 2 +- qa/qa/specs/features/repository/push_spec.rb | 2 +- qa/qa/specs/runner.rb | 8 +++- qa/spec/scenario/test/instance/all_spec.rb | 39 +++++++++++++++++++ qa/spec/scenario/test/instance/smoke_spec.rb | 45 ++++++++++++++++++++++ qa/spec/scenario/test/instance_spec.rb | 45 ---------------------- 36 files changed, 242 insertions(+), 135 deletions(-) create mode 100644 doc/development/testing_guide/smoke.md delete mode 100644 qa/qa/scenario/taggable.rb delete mode 100644 qa/qa/scenario/test/instance.rb create mode 100644 qa/qa/scenario/test/instance/all.rb create mode 100644 qa/qa/scenario/test/instance/smoke.rb create mode 100644 qa/qa/specs/features/login/basic_spec.rb create mode 100644 qa/spec/scenario/test/instance/all_spec.rb create mode 100644 qa/spec/scenario/test/instance/smoke_spec.rb delete mode 100644 qa/spec/scenario/test/instance_spec.rb diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md new file mode 100644 index 00000000000..3f2843cba6e --- /dev/null +++ b/doc/development/testing_guide/smoke.md @@ -0,0 +1,16 @@ +# Smoke Tests + +It is imperative in any testing suite that we have Smoke Tests. In short, smoke tests are will run quick sanity +end-to-end functional tests from GitLab QA and are designed to run against the specified environment to ensure that +basic functionality is working. + +Currently, our suite consists of this basic functionality coverage: + +- User Login (Standard Auth) +- Project Creation +- Issue Creation +- Merge Request Creation + +--- + +[Return to Testing documentation](index.md) diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md index 07ced36f0c1..4403072e96f 100644 --- a/doc/development/testing_guide/testing_levels.md +++ b/doc/development/testing_guide/testing_levels.md @@ -120,6 +120,14 @@ running feature tests (i.e. using Capybara) against it. The actual test scenarios and steps are [part of GitLab Rails] so that they're always in-sync with the codebase. +### Smoke tests + +Smoke tests are quick tests that may be run at any time (especially after the pre-deployment migrations). + +Much like feature tests - these tests run against the UI and ensure that basic functionality is working. + +> See [Smoke Tests](smoke.md) for more information. + Read a separate document about [end-to-end tests](end_to_end_tests.md) to learn more. diff --git a/qa/README.md b/qa/README.md index be4cf89ebbc..f8a5c00efd4 100644 --- a/qa/README.md +++ b/qa/README.md @@ -35,7 +35,7 @@ following call would login to a local [GDK] instance and run all specs in `qa/specs/features`: ``` -bin/qa Test::Instance http://localhost:3000 +bin/qa Test::Instance::All http://localhost:3000 ``` ### Writing tests @@ -48,14 +48,14 @@ You can also supply specific tests to run as another parameter. For example, to run the repository-related specs, you can execute: ``` -bin/qa Test::Instance http://localhost qa/specs/features/repository/ +bin/qa Test::Instance::All http://localhost qa/specs/features/repository/ ``` Since the arguments would be passed to `rspec`, you could use all `rspec` options there. For example, passing `--backtrace` and also line number: ``` -bin/qa Test::Instance http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace +bin/qa Test::Instance::All http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace ``` ### Overriding the authenticated user @@ -67,7 +67,7 @@ If you need to authenticate as a different user, you can provide the `GITLAB_USERNAME` and `GITLAB_PASSWORD` environment variables: ``` -GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance https://gitlab.example.com +GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance::All https://gitlab.example.com ``` If your user doesn't have permission to default sandbox group @@ -75,13 +75,13 @@ If your user doesn't have permission to default sandbox group `GITLAB_SANDBOX_NAME`: ``` -GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com +GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance::All https://gitlab.example.com ``` In addition, the `GITLAB_USER_TYPE` can be set to "ldap" to sign in as an LDAP user: ``` -GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com +GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance::All https://gitlab.example.com ``` All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables). diff --git a/qa/qa.rb b/qa/qa.rb index 0b48cf58766..4b927067449 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -77,14 +77,16 @@ module QA # autoload :Bootable, 'qa/scenario/bootable' autoload :Actable, 'qa/scenario/actable' - autoload :Taggable, 'qa/scenario/taggable' autoload :Template, 'qa/scenario/template' ## # Test scenario entrypoints. # module Test - autoload :Instance, 'qa/scenario/test/instance' + module Instance + autoload :All, 'qa/scenario/test/instance/all' + autoload :Smoke, 'qa/scenario/test/instance/smoke' + end module Integration autoload :Github, 'qa/scenario/test/integration/github' diff --git a/qa/qa/scenario/taggable.rb b/qa/qa/scenario/taggable.rb deleted file mode 100644 index b1f24d742e0..00000000000 --- a/qa/qa/scenario/taggable.rb +++ /dev/null @@ -1,17 +0,0 @@ -module QA - module Scenario - module Taggable - # rubocop:disable Gitlab/ModuleWithInstanceVariables - - def tags(*tags) - @tags = tags - end - - def focus - @tags.to_a - end - - # rubocop:enable Gitlab/ModuleWithInstanceVariables - end - end -end diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb index d21a9d52997..765b7d317e0 100644 --- a/qa/qa/scenario/template.rb +++ b/qa/qa/scenario/template.rb @@ -1,15 +1,36 @@ module QA module Scenario class Template - def self.perform(*args) - new.tap do |scenario| - yield scenario if block_given? - break scenario.perform(*args) + class << self + def perform(*args) + new.tap do |scenario| + yield scenario if block_given? + break scenario.perform(*args) + end + end + + def tags(*tags) + @tags = tags + end + + def focus + @tags.to_a end end - def perform(*_args) - raise NotImplementedError + def perform(address, *rspec_options) + Runtime::Scenario.define(:gitlab_address, address) + + Specs::Runner.perform do |specs| + specs.tty = true + specs.tags = self.class.focus + specs.options = + if rspec_options.any? + rspec_options + else + File.expand_path('../../specs/features', __dir__) + end + end end end end diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb deleted file mode 100644 index 46eb2dabb11..00000000000 --- a/qa/qa/scenario/test/instance.rb +++ /dev/null @@ -1,36 +0,0 @@ -module QA - module Scenario - module Test - ## - # Base class for running the suite against any GitLab instance, - # including staging and on-premises installation. - # - class Instance < Template - include Bootable - extend Taggable - - tags :core - - def perform(address, *rspec_options) - Runtime::Scenario.define(:gitlab_address, address) - - ## - # Perform before hooks, which are different for CE and EE - # - Runtime::Release.perform_before_hooks - - Specs::Runner.perform do |specs| - specs.tty = true - specs.tags = self.class.focus - specs.options = - if rspec_options.any? - rspec_options - else - ::File.expand_path('../../specs/features', __dir__) - end - end - end - end - end - end -end diff --git a/qa/qa/scenario/test/instance/all.rb b/qa/qa/scenario/test/instance/all.rb new file mode 100644 index 00000000000..a07c26431bd --- /dev/null +++ b/qa/qa/scenario/test/instance/all.rb @@ -0,0 +1,15 @@ +module QA + module Scenario + module Test + ## + # Base class for running the suite against any GitLab instance, + # including staging and on-premises installation. + # + module Instance + class All < Template + include Bootable + end + end + end + end +end diff --git a/qa/qa/scenario/test/instance/smoke.rb b/qa/qa/scenario/test/instance/smoke.rb new file mode 100644 index 00000000000..a7d2cb27f27 --- /dev/null +++ b/qa/qa/scenario/test/instance/smoke.rb @@ -0,0 +1,17 @@ +module QA + module Scenario + module Test + module Instance + ## + # Base class for running the suite against any GitLab instance, + # including staging and on-premises installation. + # + class Smoke < Template + include Bootable + + tags :smoke + end + end + end + end +end diff --git a/qa/qa/specs/features/api/basics_spec.rb b/qa/qa/specs/features/api/basics_spec.rb index 6563b56d1b4..7defb2ea93e 100644 --- a/qa/qa/specs/features/api/basics_spec.rb +++ b/qa/qa/specs/features/api/basics_spec.rb @@ -1,7 +1,7 @@ require 'securerandom' module QA - describe 'API basics', :core do + describe 'API basics' do before(:context) do @api_client = Runtime::API::Client.new(:gitlab) end diff --git a/qa/qa/specs/features/api/users_spec.rb b/qa/qa/specs/features/api/users_spec.rb index 8a63d8095c9..2a9360734bb 100644 --- a/qa/qa/specs/features/api/users_spec.rb +++ b/qa/qa/specs/features/api/users_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'API users', :core do + describe 'API users' do before(:context) do @api_client = Runtime::API::Client.new(:gitlab) end diff --git a/qa/qa/specs/features/login/basic_spec.rb b/qa/qa/specs/features/login/basic_spec.rb new file mode 100644 index 00000000000..f866466c7bf --- /dev/null +++ b/qa/qa/specs/features/login/basic_spec.rb @@ -0,0 +1,15 @@ +module QA + describe 'basic user login', :smoke do + it 'user logs in using basic credentials' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + # TODO, since `Signed in successfully` message was removed + # this is the only way to tell if user is signed in correctly. + # + Page::Menu::Main.perform do |menu| + expect(menu).to have_personal_area + end + end + end +end diff --git a/qa/qa/specs/features/login/ldap_spec.rb b/qa/qa/specs/features/login/ldap_spec.rb index b7a284c584b..de6111eea64 100644 --- a/qa/qa/specs/features/login/ldap_spec.rb +++ b/qa/qa/specs/features/login/ldap_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'LDAP user login', :ldap do + describe 'LDAP user login', :orchestrated, :ldap do before do Runtime::Env.user_type = 'ldap' end diff --git a/qa/qa/specs/features/mattermost/group_create_spec.rb b/qa/qa/specs/features/mattermost/group_create_spec.rb index a59761da341..097e1713aef 100644 --- a/qa/qa/specs/features/mattermost/group_create_spec.rb +++ b/qa/qa/specs/features/mattermost/group_create_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'create a new group', :mattermost do + describe 'create a new group', :orchestrated, :mattermost do it 'creating a group with a mattermost team' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/mattermost/login_spec.rb b/qa/qa/specs/features/mattermost/login_spec.rb index b140191e160..27f7d4c245f 100644 --- a/qa/qa/specs/features/mattermost/login_spec.rb +++ b/qa/qa/specs/features/mattermost/login_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'logging in to Mattermost', :mattermost do + describe 'logging in to Mattermost', :orchestrated, :mattermost do it 'can use gitlab oauth' do Runtime::Browser.visit(:gitlab, Page::Main::Login) do Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/merge_request/create_spec.rb b/qa/qa/specs/features/merge_request/create_spec.rb index 36d7efb02e1..71e79956b85 100644 --- a/qa/qa/specs/features/merge_request/create_spec.rb +++ b/qa/qa/specs/features/merge_request/create_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'creates a merge request', :core do + describe 'creates a merge request with milestone' do it 'user creates a new merge request' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } @@ -29,4 +29,25 @@ module QA end end end + + describe 'creates a merge request', :smoke do + it 'user creates a new merge request' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + current_project = Factory::Resource::Project.fabricate! do |project| + project.name = 'project-with-merge-request' + end + + Factory::Resource::MergeRequest.fabricate! do |merge_request| + merge_request.title = 'This is a merge request' + merge_request.description = 'Great feature' + merge_request.project = current_project + end + + expect(page).to have_content('This is a merge request') + expect(page).to have_content('Great feature') + expect(page).to have_content(/Opened [\w\s]+ ago/) + end + end end diff --git a/qa/qa/specs/features/merge_request/rebase_spec.rb b/qa/qa/specs/features/merge_request/rebase_spec.rb index 163dcbe7963..c36d28e4237 100644 --- a/qa/qa/specs/features/merge_request/rebase_spec.rb +++ b/qa/qa/specs/features/merge_request/rebase_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'merge request rebase', :core do + describe 'merge request rebase' do it 'rebases source branch of merge request' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/merge_request/squash_spec.rb b/qa/qa/specs/features/merge_request/squash_spec.rb index 4856bbe1a69..3ecc36a5ae1 100644 --- a/qa/qa/specs/features/merge_request/squash_spec.rb +++ b/qa/qa/specs/features/merge_request/squash_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'merge request squash commits', :core do + describe 'merge request squash commits' do it 'when squash commits is marked before merge' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/project/activity_spec.rb b/qa/qa/specs/features/project/activity_spec.rb index 02074e386b6..c7ce8dfdcc6 100644 --- a/qa/qa/specs/features/project/activity_spec.rb +++ b/qa/qa/specs/features/project/activity_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'activity page', :core do + describe 'activity page' do it 'push creates an event in the activity page' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb index 14642af97ad..24f9f4c77f8 100644 --- a/qa/qa/specs/features/project/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'deploy keys support', :core do + describe 'deploy keys support' do it 'user adds a deploy key' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/project/add_secret_variable_spec.rb b/qa/qa/specs/features/project/add_secret_variable_spec.rb index 32c91dd9d4d..04d9fe488e2 100644 --- a/qa/qa/specs/features/project/add_secret_variable_spec.rb +++ b/qa/qa/specs/features/project/add_secret_variable_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'secret variables support', :core do + describe 'secret variables support' do it 'user adds a secret variable' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/project/auto_devops_spec.rb b/qa/qa/specs/features/project/auto_devops_spec.rb index b3e4889abc0..248669b6046 100644 --- a/qa/qa/specs/features/project/auto_devops_spec.rb +++ b/qa/qa/specs/features/project/auto_devops_spec.rb @@ -1,7 +1,7 @@ require 'pathname' module QA - describe 'Auto Devops', :kubernetes do + describe 'Auto Devops', :orchestrated, :kubernetes do after do @cluster&.remove! end diff --git a/qa/qa/specs/features/project/create_issue_spec.rb b/qa/qa/specs/features/project/create_issue_spec.rb index ac2ed2ca2a1..793e7db87cb 100644 --- a/qa/qa/specs/features/project/create_issue_spec.rb +++ b/qa/qa/specs/features/project/create_issue_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'creates issue', :core do + describe 'creates issue', :smoke do let(:issue_title) { 'issue title' } it 'user creates issue' do diff --git a/qa/qa/specs/features/project/create_spec.rb b/qa/qa/specs/features/project/create_spec.rb index 14ecd464685..5e19e490778 100644 --- a/qa/qa/specs/features/project/create_spec.rb +++ b/qa/qa/specs/features/project/create_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'create a new project', :core do + describe 'create a new project', :smoke do it 'user creates a new project' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/project/deploy_key_clone_spec.rb b/qa/qa/specs/features/project/deploy_key_clone_spec.rb index 054f49b408a..1d099508c24 100644 --- a/qa/qa/specs/features/project/deploy_key_clone_spec.rb +++ b/qa/qa/specs/features/project/deploy_key_clone_spec.rb @@ -1,7 +1,7 @@ require 'digest/sha1' module QA - describe 'cloning code using a deploy key', :core, :docker do + describe 'cloning code using a deploy key', :docker do def login Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/project/fork_project_spec.rb b/qa/qa/specs/features/project/fork_project_spec.rb index 8ad0120305a..d3534d736e4 100644 --- a/qa/qa/specs/features/project/fork_project_spec.rb +++ b/qa/qa/specs/features/project/fork_project_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'Project fork', :core do + describe 'Project fork' do it 'can submit merge requests to upstream master' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/project/import_from_github_spec.rb b/qa/qa/specs/features/project/import_from_github_spec.rb index 221b5c27fba..57695d2c726 100644 --- a/qa/qa/specs/features/project/import_from_github_spec.rb +++ b/qa/qa/specs/features/project/import_from_github_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'user imports a GitHub repo', :core, :github do + describe 'user imports a GitHub repo', :orchestrated, :github do let(:imported_project) do Factory::Resource::ProjectImportedFromGithub.fabricate! do |project| project.name = 'imported-project' diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index ddedde7a8bc..6c6b4e80626 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'CI/CD Pipelines', :core, :docker do + describe 'CI/CD Pipelines', :orchestrated, :docker do let(:executor) { "qa-runner-#{Time.now.to_i}" } after do diff --git a/qa/qa/specs/features/project/wikis_spec.rb b/qa/qa/specs/features/project/wikis_spec.rb index 59cc455fffc..9af2dbd1264 100644 --- a/qa/qa/specs/features/project/wikis_spec.rb +++ b/qa/qa/specs/features/project/wikis_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'Wiki Functionality', :core do + describe 'Wiki Functionality' do def login Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/repository/clone_spec.rb b/qa/qa/specs/features/repository/clone_spec.rb index a04ce4e44d9..8b0613c5f78 100644 --- a/qa/qa/specs/features/repository/clone_spec.rb +++ b/qa/qa/specs/features/repository/clone_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'clone code from the repository', :core do + describe 'clone code from the repository' do context 'with regular account over http' do let(:location) do Page::Project::Show.act do diff --git a/qa/qa/specs/features/repository/protected_branches_spec.rb b/qa/qa/specs/features/repository/protected_branches_spec.rb index c2de94516d9..aa23145478d 100644 --- a/qa/qa/specs/features/repository/protected_branches_spec.rb +++ b/qa/qa/specs/features/repository/protected_branches_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'branch protection support', :core do + describe 'branch protection support' do let(:branch_name) { 'protected-branch' } let(:commit_message) { 'Protected push commit message' } let(:project) do diff --git a/qa/qa/specs/features/repository/push_spec.rb b/qa/qa/specs/features/repository/push_spec.rb index fc40b60d915..1e89942e932 100644 --- a/qa/qa/specs/features/repository/push_spec.rb +++ b/qa/qa/specs/features/repository/push_spec.rb @@ -1,5 +1,5 @@ module QA - describe 'push code to repository', :core do + describe 'push code to repository' do context 'with regular account over http' do it 'user pushes code to the repository' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index f8f6fe65599..ccb9d5591de 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -14,7 +14,13 @@ module QA def perform args = [] args.push('--tty') if tty - tags.to_a.each { |tag| args.push(['-t', tag.to_s]) } + + if tags.any? + tags.each { |tag| args.push(['-t', tag.to_s]) } + else + args.push(%w[-t ~orchestrated]) + end + args.push(options) Runtime::Browser.configure! diff --git a/qa/spec/scenario/test/instance/all_spec.rb b/qa/spec/scenario/test/instance/all_spec.rb new file mode 100644 index 00000000000..bc0b21c6494 --- /dev/null +++ b/qa/spec/scenario/test/instance/all_spec.rb @@ -0,0 +1,39 @@ +describe QA::Scenario::Test::Instance::All do + context '#perform' do + let(:arguments) { spy('Runtime::Scenario') } + let(:release) { spy('Runtime::Release') } + let(:runner) { spy('Specs::Runner') } + + before do + stub_const('QA::Runtime::Release', release) + stub_const('QA::Runtime::Scenario', arguments) + stub_const('QA::Specs::Runner', runner) + + allow(runner).to receive(:perform).and_yield(runner) + end + + it 'sets an address of the subject' do + subject.perform("hello") + + expect(arguments).to have_received(:define) + .with(:gitlab_address, "hello") + end + + context 'no paths' do + it 'calls runner with default arguments' do + subject.perform("test") + + expect(runner).to have_received(:options=) + .with(::File.expand_path('../../../../../qa/specs/features', __dir__)) + end + end + + context 'specifying paths' do + it 'calls runner with paths' do + subject.perform('test', 'path1', 'path2') + + expect(runner).to have_received(:options=).with(%w[path1 path2]) + end + end + end +end diff --git a/qa/spec/scenario/test/instance/smoke_spec.rb b/qa/spec/scenario/test/instance/smoke_spec.rb new file mode 100644 index 00000000000..66d71610341 --- /dev/null +++ b/qa/spec/scenario/test/instance/smoke_spec.rb @@ -0,0 +1,45 @@ +describe QA::Scenario::Test::Instance::Smoke do + subject { Class.new(described_class) { tags :smoke } } + + context '#perform' do + let(:arguments) { spy('Runtime::Scenario') } + let(:release) { spy('Runtime::Release') } + let(:runner) { spy('Specs::Runner') } + + before do + stub_const('QA::Runtime::Release', release) + stub_const('QA::Runtime::Scenario', arguments) + stub_const('QA::Specs::Runner', runner) + + allow(runner).to receive(:perform).and_yield(runner) + end + + it 'sets an address of the subject' do + subject.perform("hello") + + expect(arguments).to have_received(:define) + .with(:gitlab_address, "hello") + end + + it 'has a smoke tag' do + expect(subject.focus).to eq([:smoke]) # rubocop:disable Focus + end + + context 'no paths' do + it 'calls runner with default arguments' do + subject.perform("test") + + expect(runner).to have_received(:options=) + .with(File.expand_path('../../../../../qa/specs/features', __dir__)) + end + end + + context 'specifying paths' do + it 'calls runner with paths' do + subject.perform('test', 'path1', 'path2') + + expect(runner).to have_received(:options=).with(%w[path1 path2]) + end + end + end +end diff --git a/qa/spec/scenario/test/instance_spec.rb b/qa/spec/scenario/test/instance_spec.rb deleted file mode 100644 index 0d0b534911f..00000000000 --- a/qa/spec/scenario/test/instance_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -describe QA::Scenario::Test::Instance do - subject do - Class.new(described_class) do - tags :rspec - end - end - - context '#perform' do - let(:arguments) { spy('Runtime::Scenario') } - let(:release) { spy('Runtime::Release') } - let(:runner) { spy('Specs::Runner') } - - before do - stub_const('QA::Runtime::Release', release) - stub_const('QA::Runtime::Scenario', arguments) - stub_const('QA::Specs::Runner', runner) - - allow(runner).to receive(:perform).and_yield(runner) - end - - it 'sets an address of the subject' do - subject.perform("hello") - - expect(arguments).to have_received(:define) - .with(:gitlab_address, "hello") - end - - context 'no paths' do - it 'should call runner with default arguments' do - subject.perform("test") - - expect(runner).to have_received(:options=) - .with(::File.expand_path('../../../qa/specs/features', __dir__)) - end - end - - context 'specifying paths' do - it 'should call runner with paths' do - subject.perform('test', 'path1', 'path2') - - expect(runner).to have_received(:options=).with(%w[path1 path2]) - end - end - end -end -- cgit v1.2.1 From a89883b1c91928c6171b309a86ba425965dfbd54 Mon Sep 17 00:00:00 2001 From: Adriel Santiago <asantiago@gitlab.com> Date: Mon, 20 Aug 2018 20:03:57 +0000 Subject: Resolve "Remove redundant header from metrics page" --- app/assets/javascripts/monitoring/components/dashboard.vue | 2 +- app/views/projects/environments/metrics.html.haml | 7 ------- .../unreleased/50019-remove-redundant-header-from-metrics-page.yml | 5 +++++ 3 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/50019-remove-redundant-header-from-metrics-page.yml diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 6afaefc56f8..ae96ac3b80c 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -172,7 +172,7 @@ export default { <template> <div v-if="!showEmptyState" - class="prometheus-graphs prepend-top-10" + class="prometheus-graphs prepend-top-default" > <div class="environments d-flex align-items-center"> {{ s__('Metrics|Environment') }} diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index af86b8e8e67..4222963a754 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -2,11 +2,4 @@ - page_title "Metrics for environment", @environment.name .prometheus-container{ class: container_class } - .top-area - .row - .col-sm-6 - %h3 - Environment: - = link_to @environment.name, environment_path(@environment) - #prometheus-graphs{ data: metrics_data(@project, @environment) } diff --git a/changelogs/unreleased/50019-remove-redundant-header-from-metrics-page.yml b/changelogs/unreleased/50019-remove-redundant-header-from-metrics-page.yml new file mode 100644 index 00000000000..8057819b223 --- /dev/null +++ b/changelogs/unreleased/50019-remove-redundant-header-from-metrics-page.yml @@ -0,0 +1,5 @@ +--- +title: Remove redundant header from metrics page +merge_request: 21282 +author: +type: changed -- cgit v1.2.1