From fd88b0ca56b3a4230902f76a7b049228e53e6bb0 Mon Sep 17 00:00:00 2001 From: Tony Rom Date: Thu, 16 Nov 2017 21:10:15 +0300 Subject: Add `pipelines` endpoint to merge requests API --- changelogs/unreleased/39214__pipeline_api.yml | 5 +++ doc/api/merge_requests.md | 26 +++++++++++++++ lib/api/merge_requests.rb | 15 +++++++++ .../api/schemas/public_api/v4/pipelines.json | 4 +++ spec/requests/api/merge_requests_spec.rb | 37 ++++++++++++++++++++++ 5 files changed, 87 insertions(+) create mode 100644 changelogs/unreleased/39214__pipeline_api.yml create mode 100644 spec/fixtures/api/schemas/public_api/v4/pipelines.json diff --git a/changelogs/unreleased/39214__pipeline_api.yml b/changelogs/unreleased/39214__pipeline_api.yml new file mode 100644 index 00000000000..18ee2e43798 --- /dev/null +++ b/changelogs/unreleased/39214__pipeline_api.yml @@ -0,0 +1,5 @@ +--- +title: Add `pipelines` endpoint to merge requests API +merge_request: 15454 +author: Tony Rom +type: added diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 880b0ed2c65..a3261cf7ee4 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -431,6 +431,32 @@ Parameters: } ``` +## List MR pipelines + +Get a list of merge request pipelines. + +``` +GET /projects/:id/merge_requests/:merge_request_iid/pipelines +``` + +Parameters: + +- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +- `merge_request_iid` (required) - The internal ID of the merge request + +Example of response + +```json +[ + { + "id": 77, + "sha": "959e04d7c7a30600c894bd3c0cd0e1ce7f42c11d", + "ref": "master", + "status": "success" + } +] +``` + ## Create MR Creates a new merge request. diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d34886fca2e..77c563ec0b4 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -24,6 +24,12 @@ module API .preload(:notes, :author, :assignee, :milestone, :latest_merge_request_diff, :labels, :timelogs) end + def merge_request_pipelines_with_access(access_level = :read_pipeline) + authorize! access_level, user_project + mr = find_merge_request_with_access(params[:merge_request_iid]) + mr.all_pipelines + end + params :merge_requests_params do optional :state, type: String, values: %w[opened closed merged all], default: 'all', desc: 'Return opened, closed, merged, or all merge requests' @@ -203,6 +209,15 @@ module API present merge_request, with: Entities::MergeRequestChanges, current_user: current_user end + desc 'Get the merge request pipelines' do + success Entities::PipelineBasic + end + get ':id/merge_requests/:merge_request_iid/pipelines' do + pipelines = merge_request_pipelines_with_access + + present paginate(pipelines), with: Entities::PipelineBasic + end + desc 'Update a merge request' do success Entities::MergeRequest end diff --git a/spec/fixtures/api/schemas/public_api/v4/pipelines.json b/spec/fixtures/api/schemas/public_api/v4/pipelines.json new file mode 100644 index 00000000000..8b08a00f708 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/pipelines.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "pipeline/basic.json" } +} diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 91616da6d9a..4278e32dc78 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -525,6 +525,43 @@ describe API::MergeRequests do end end + describe 'GET /projects/:id/merge_requests/:merge_request_iid/pipelines' do + context 'when authorized' do + let!(:pipeline) { create(:ci_empty_pipeline, project: project, user: user, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) } + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + + it 'returns a paginated array of corresponding pipelines' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines") + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.count).to eq(1) + expect(json_response.first['id']).to eq(pipeline.id) + end + + it 'exposes basic attributes' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/pipelines') + end + end + + context 'when unauthorized' do + it 'returns 403' do + project = create(:project, public_builds: false) + merge_request = create(:merge_request, :simple, source_project: project) + guest = create(:user) + project.team << [guest, :guest] + + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines", guest) + + expect(response).to have_gitlab_http_status(403) + end + end + end + describe "POST /projects/:id/merge_requests" do context 'between branches projects' do it "returns merge_request" do -- cgit v1.2.1 From 32424e461bebebae2ad1bf302e8fb0d375771135 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 11:51:25 +0100 Subject: Add QA sanity selectors scenario entrypoint --- qa/qa.rb | 4 ++++ qa/qa/scenario/test/sanity/selectors.rb | 14 ++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 qa/qa/scenario/test/sanity/selectors.rb diff --git a/qa/qa.rb b/qa/qa.rb index 340f5e35c67..6a878400287 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -56,6 +56,10 @@ module QA module Integration autoload :Mattermost, 'qa/scenario/test/integration/mattermost' end + + module Sanity + autoload :Selectors, 'qa/scenario/test/sanity/selectors' + end end end diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb new file mode 100644 index 00000000000..892bb2966c7 --- /dev/null +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -0,0 +1,14 @@ +module QA + module Scenario + module Test + module Sanity + class Selectors < Scenario::Template + include Scenario::Bootable + + def perform(*) + end + end + end + end + end +end -- cgit v1.2.1 From 208411ee6242931670bdef5a8c92d34dee18498e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 11:54:08 +0100 Subject: Add QA classes that represent view partials and elements --- qa/qa.rb | 2 ++ qa/qa/page/element.rb | 6 ++++++ qa/qa/page/view.rb | 6 ++++++ 3 files changed, 14 insertions(+) create mode 100644 qa/qa/page/element.rb create mode 100644 qa/qa/page/view.rb diff --git a/qa/qa.rb b/qa/qa.rb index 6a878400287..fb3b646564a 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -70,6 +70,8 @@ module QA # module Page autoload :Base, 'qa/page/base' + autoload :View, 'qa/page/view' + autoload :Element, 'qa/page/element' module Main autoload :Login, 'qa/page/main/login' diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb new file mode 100644 index 00000000000..c634e834c96 --- /dev/null +++ b/qa/qa/page/element.rb @@ -0,0 +1,6 @@ +module QA + module Page + class Element + end + end +end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb new file mode 100644 index 00000000000..076e4b8061b --- /dev/null +++ b/qa/qa/page/view.rb @@ -0,0 +1,6 @@ +module QA + module Page + class View + end + end +end -- cgit v1.2.1 From 856917520de2b1b254400b4501b4b98991280736 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 13:01:12 +0100 Subject: Add domain specific language to define QA page elements --- qa/qa/page/base.rb | 21 +++++++++++++++++++++ qa/qa/page/element.rb | 26 ++++++++++++++++++++++++++ qa/qa/page/view.rb | 6 ++++++ qa/spec/page/base_spec.rb | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 qa/spec/page/base_spec.rb diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 99eba02b6e3..4f79f24a629 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -5,6 +5,9 @@ module QA class Base include Capybara::DSL include Scenario::Actable + extend SingleForwardable + + def_delegators :evaluator, :view, :views def refresh visit current_url @@ -40,6 +43,24 @@ module QA def self.path raise NotImplementedError end + + def self.evaluator + @evaluator ||= Page::Base::DSL.new + end + + class DSL + attr_reader :views + + def initialize + @views = [] + end + + def view(path, &block) + Page::Element.evaluate(&block).tap do |elements| + @views.push(Page::View.new(path, elements)) + end + end + end end end end diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index c634e834c96..e8e537070cb 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -1,6 +1,32 @@ module QA module Page class Element + attr_reader :name + + def initialize(name, pattern) + @name = name + @pattern = pattern + end + + def self.evaluate(&block) + Page::Element::DSL.new.tap do |evaluator| + evaluator.instance_exec(&block) + + return evaluator.elements + end + end + + class DSL + attr_reader :elements + + def initialize + @elements = [] + end + + def element(name, pattern) + @elements.push(Page::Element.new(name, pattern)) + end + end end end end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index 076e4b8061b..c54a0d738bc 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -1,6 +1,12 @@ module QA module Page class View + attr_reader :path, :elements + + def initialize(path, elements) + @path = path + @elements = elements + end end end end diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb new file mode 100644 index 00000000000..31ff9e258a1 --- /dev/null +++ b/qa/spec/page/base_spec.rb @@ -0,0 +1,35 @@ +describe QA::Page::Base do + describe 'page helpers' do + it 'exposes helpful page helpers' do + expect(subject).to respond_to :refresh, :wait, :scroll_to + end + end + + describe 'DSL for defining view partials', '.view' do + subject do + Class.new(described_class) do + view 'path/to/some/view.html.haml' do + element :something, 'string pattern' + element :something_else, /regexp pattern/ + end + + view 'path/to/some/_partial.html.haml' do + element :something, 'string pattern' + end + end + end + + it 'makes it possible to define page views' do + expect(subject.views.size).to eq 2 + expect(subject.views).to all(be_an_instance_of QA::Page::View) + end + + it 'populates views objects with data about elements' do + subject.views.first.elements.tap do |elements| + expect(elements.size).to eq 2 + expect(elements).to all(be_an_instance_of QA::Page::Element) + expect(elements.map(&:name)).to eq [:something, :something_else] + end + end + end +end -- cgit v1.2.1 From b51ba96e4dddd847e42f0e41c3e1df2ff58d42e4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 13:13:09 +0100 Subject: Move QA elements DSL as it belongs to different class --- qa/qa/page/base.rb | 4 ++-- qa/qa/page/element.rb | 20 -------------------- qa/qa/page/view.rb | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 4f79f24a629..d67407e7408 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -56,8 +56,8 @@ module QA end def view(path, &block) - Page::Element.evaluate(&block).tap do |elements| - @views.push(Page::View.new(path, elements)) + Page::View.evaluate(&block).tap do |view| + @views.push(Page::View.new(path, view.elements)) end end end diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index e8e537070cb..fae5c81fbc2 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -7,26 +7,6 @@ module QA @name = name @pattern = pattern end - - def self.evaluate(&block) - Page::Element::DSL.new.tap do |evaluator| - evaluator.instance_exec(&block) - - return evaluator.elements - end - end - - class DSL - attr_reader :elements - - def initialize - @elements = [] - end - - def element(name, pattern) - @elements.push(Page::Element.new(name, pattern)) - end - end end end end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index c54a0d738bc..adec88e9918 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -7,6 +7,24 @@ module QA @path = path @elements = elements end + + def self.evaluate(&block) + Page::View::DSL.new.tap do |evaluator| + evaluator.instance_exec(&block) + end + end + + class DSL + attr_reader :elements + + def initialize + @elements = [] + end + + def element(name, pattern) + @elements.push(Page::Element.new(name, pattern)) + end + end end end end -- cgit v1.2.1 From 481f461380d4919077c543c51f58e37337167706 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 15:22:54 +0100 Subject: Add implementation for matching view elements in QA --- qa/qa/page/base.rb | 4 ++++ qa/qa/page/element.rb | 12 ++++++++++ qa/qa/page/validator.rb | 18 ++++++++++++++ qa/qa/page/view.rb | 21 ++++++++++++++++ qa/spec/page/view_spec.rb | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 qa/qa/page/validator.rb create mode 100644 qa/spec/page/view_spec.rb diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index d67407e7408..48ac0569596 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -48,6 +48,10 @@ module QA @evaluator ||= Page::Base::DSL.new end + def self.validator + Page::Validator.new(self) + end + class DSL attr_reader :views diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index fae5c81fbc2..c47776bb2f6 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -7,6 +7,18 @@ module QA @name = name @pattern = pattern end + + def expression? + @pattern.is_a?(Regexp) + end + + def matches?(line) + if expression? + line =~ pattern + else + line.includes?(pattern) + end + end end end end diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb new file mode 100644 index 00000000000..bd9511595c5 --- /dev/null +++ b/qa/qa/page/validator.rb @@ -0,0 +1,18 @@ +module QA + module Page + class Validator + def initialize(page) + @page = page + @views = page.views + end + + def errors + @errors ||= @views.map do |view| + end + end + + def message + end + end + end +end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index adec88e9918..ec20045e26d 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -8,6 +8,27 @@ module QA @elements = elements end + def pathname + Pathname.new(File.join( __dir__, '../../../', @path)) + .cleanpath.expand_path + end + + def errors + ## + # Reduce required elements by streaming views and making assertions on + # elements' patterns. + # + @missing ||= @elements.dup.tap do |elements| + File.new(pathname.to_s).foreach do |line| + elements.reject! { |element| element.matches?(line) } + end + end + + @missing.map do |missing| + "Missing element `#{missing}` in `#{pathname}` view partial!" + end + end + def self.evaluate(&block) Page::View::DSL.new.tap do |evaluator| evaluator.instance_exec(&block) diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb new file mode 100644 index 00000000000..27e83d35de1 --- /dev/null +++ b/qa/spec/page/view_spec.rb @@ -0,0 +1,61 @@ +describe QA::Page::View do + let(:element) do + double('element', name: :something, pattern: /some element/) + end + + subject { described_class.new('some/file.html', [element]) } + + describe '.evaluate' do + it 'evaluates a block and returns a DSL object' do + results = described_class.evaluate do + element :something, 'my pattern' + element :something_else, /another pattern/ + end + + expect(results.elements.size).to eq 2 + end + end + + describe '#pathname' do + it 'returns an absolute and clean path to the view' do + expect(subject.pathname.to_s).not_to include 'qa/page/' + expect(subject.pathname.to_s).to include 'some/file.html' + end + end + + describe '#errors' do + let(:file) { spy('file') } + + before do + allow(File).to receive(:new).and_return(file) + end + + context 'when pattern is found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(true) + end + + it 'walks through the view and asserts on elements existence' do + expect(subject.errors).to be_empty + end + end + + context 'when pattern has not been found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(false) + end + + it 'returns an array of errors related to missing elements' do + expect(subject.errors).not_to be_empty + expect(subject.errors.first) + .to match %r(Missing element `.*` in `.*/some/file.html` view) + end + end + end +end -- cgit v1.2.1 From d69e4541a4874208590a7387186f8929143fd2af Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 15:40:46 +0100 Subject: Use composite pattern to return page view errors --- qa/qa/page/base.rb | 4 ++-- qa/qa/page/element.rb | 13 ++++++------- qa/qa/page/validator.rb | 18 ------------------ qa/qa/page/view.rb | 4 ++-- qa/spec/page/base_spec.rb | 17 ++++++++++++++++- qa/spec/page/element_spec.rb | 34 ++++++++++++++++++++++++++++++++++ qa/spec/page/view_spec.rb | 4 ++++ 7 files changed, 64 insertions(+), 30 deletions(-) delete mode 100644 qa/qa/page/validator.rb create mode 100644 qa/spec/page/element_spec.rb diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 48ac0569596..9064c78b792 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -48,8 +48,8 @@ module QA @evaluator ||= Page::Base::DSL.new end - def self.validator - Page::Validator.new(self) + def self.errors + @errors ||= views.map(&:errors).flatten end class DSL diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index c47776bb2f6..173fdbf6d36 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -8,15 +8,14 @@ module QA @pattern = pattern end - def expression? - @pattern.is_a?(Regexp) - end - def matches?(line) - if expression? - line =~ pattern + case @pattern + when Regexp + !!(line =~ @pattern) + when String + line.include?(@pattern) else - line.includes?(pattern) + raise ArgumentError, 'Pattern should be either String or Regexp!' end end end diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb deleted file mode 100644 index bd9511595c5..00000000000 --- a/qa/qa/page/validator.rb +++ /dev/null @@ -1,18 +0,0 @@ -module QA - module Page - class Validator - def initialize(page) - @page = page - @views = page.views - end - - def errors - @errors ||= @views.map do |view| - end - end - - def message - end - end - end -end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index ec20045e26d..b988b179e8f 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -15,8 +15,8 @@ module QA def errors ## - # Reduce required elements by streaming views and making assertions on - # elements' patterns. + # Reduce required elements by streaming view and making assertions on + # elements' existence. # @missing ||= @elements.dup.tap do |elements| File.new(pathname.to_s).foreach do |line| diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb index 31ff9e258a1..63445d8f7bf 100644 --- a/qa/spec/page/base_spec.rb +++ b/qa/spec/page/base_spec.rb @@ -5,7 +5,7 @@ describe QA::Page::Base do end end - describe 'DSL for defining view partials', '.view' do + describe '.view', 'DSL for defining view partials' do subject do Class.new(described_class) do view 'path/to/some/view.html.haml' do @@ -32,4 +32,19 @@ describe QA::Page::Base do end end end + + describe '.errors' do + let(:view) { double('view') } + + before do + allow(described_class).to receive(:views) + .and_return([view]) + + allow(view).to receive(:errors).and_return(['some error']) + end + + it 'iterates views composite and returns errors' do + expect(described_class.errors).to eq ['some error'] + end + end end diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb new file mode 100644 index 00000000000..238c4d1ac66 --- /dev/null +++ b/qa/spec/page/element_spec.rb @@ -0,0 +1,34 @@ +describe QA::Page::Element do + context 'when pattern is an expression' do + subject { described_class.new(:something, /button 'Sign in'/) } + + it 'is correctly matches against a string' do + expect(subject.matches?("button 'Sign in'")).to be true + end + + it 'does not match if string does not match against a pattern' do + expect(subject.matches?("button 'Sign out'")).to be false + end + end + + context 'when pattern is a string' do + subject { described_class.new(:something, 'button') } + + it 'is correctly matches against a string' do + expect(subject.matches?('some button in the view')).to be true + end + + it 'does not match if string does not match against a pattern' do + expect(subject.matches?('text_field :name')).to be false + end + end + + context 'when pattern is not supported' do + subject { described_class.new(:something, [/something/]) } + + it 'raises an error' do + expect { subject.matches?('some line') } + .to raise_error ArgumentError + end + end +end diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb index 27e83d35de1..6a78e32db68 100644 --- a/qa/spec/page/view_spec.rb +++ b/qa/spec/page/view_spec.rb @@ -57,5 +57,9 @@ describe QA::Page::View do .to match %r(Missing element `.*` in `.*/some/file.html` view) end end + + context 'when view partial has not been found' do + pending + end end end -- cgit v1.2.1 From d2c2f93fe6ed557ecfbc53afedef899dd49b244d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 16:09:00 +0100 Subject: Append page validation error if view partial is missing --- qa/qa/page/view.rb | 6 +++++- qa/spec/page/view_spec.rb | 51 ++++++++++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index b988b179e8f..fa0ed8be9d9 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -9,11 +9,15 @@ module QA end def pathname - Pathname.new(File.join( __dir__, '../../../', @path)) + @pathname ||= Pathname.new(File.join( __dir__, '../../../', @path)) .cleanpath.expand_path end def errors + unless pathname.readable? + return ["Missing view partial `#{pathname}`!"] + end + ## # Reduce required elements by streaming view and making assertions on # elements' existence. diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb index 6a78e32db68..dd38b171ad5 100644 --- a/qa/spec/page/view_spec.rb +++ b/qa/spec/page/view_spec.rb @@ -30,36 +30,47 @@ describe QA::Page::View do allow(File).to receive(:new).and_return(file) end - context 'when pattern is found' do + context 'when view partial is present' do before do - allow(file).to receive(:foreach) - .and_yield('some element').once - allow(element).to receive(:matches?) - .with('some element').and_return(true) + allow(subject.pathname).to receive(:readable?) + .and_return(true) end - it 'walks through the view and asserts on elements existence' do - expect(subject.errors).to be_empty - end - end + context 'when pattern is found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(true) + end - context 'when pattern has not been found' do - before do - allow(file).to receive(:foreach) - .and_yield('some element').once - allow(element).to receive(:matches?) - .with('some element').and_return(false) + it 'walks through the view and asserts on elements existence' do + expect(subject.errors).to be_empty + end end - it 'returns an array of errors related to missing elements' do - expect(subject.errors).not_to be_empty - expect(subject.errors.first) - .to match %r(Missing element `.*` in `.*/some/file.html` view) + context 'when pattern has not been found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(false) + end + + it 'returns an array of errors related to missing elements' do + expect(subject.errors).not_to be_empty + expect(subject.errors.first) + .to match %r(Missing element `.*` in `.*/some/file.html` view) + end end end context 'when view partial has not been found' do - pending + it 'returns an error when it is not able to find the partial' do + expect(subject.errors).to be_one + expect(subject.errors.first) + .to match %r(Missing view partial `.*/some/file.html`!) + end end end end -- cgit v1.2.1 From cc171725b6feafc62b42c2570e72e51f67b1d4d5 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Wed, 3 Jan 2018 03:59:48 +0900 Subject: Make project README containers wider on fixed layout --- app/assets/stylesheets/framework/files.scss | 2 +- changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 1588036aeae..223a8b3ba2f 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -18,7 +18,7 @@ margin: $gl-padding 0; &.limited-width-container .file-content { - max-width: $limited-layout-width-sm; + max-width: $limited-layout-width; margin-left: auto; margin-right: auto; diff --git a/changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml b/changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml new file mode 100644 index 00000000000..e50f6046b17 --- /dev/null +++ b/changelogs/unreleased/41600-wider-project-readme-on-fixed-layout.yml @@ -0,0 +1,5 @@ +--- +title: Make project README containers wider on fixed layout +merge_request: 16181 +author: Takuya Noguchi +type: fixed -- cgit v1.2.1 From d0b8f536a1865af3741fc3255325b7e211ed1d42 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 2 Jan 2018 17:21:28 +0100 Subject: Remove soft removals related code This removes all usage of soft removals except for the "pending delete" system implemented for projects. This in turn simplifies all the query plans of the models that used soft removals. Since we don't really use soft removals for anything useful there's no point in keeping it around. This _does_ mean that hard removals of issues (which only admins can do if I'm not mistaken) can influence the "iid" values, but that code is broken to begin with. More on this (and how to fix it) can be found in https://gitlab.com/gitlab-org/gitlab-ce/issues/31114. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/37447 --- Gemfile | 3 - Gemfile.lock | 3 - app/models/ci/pipeline_schedule.rb | 3 +- app/models/ci/trigger.rb | 3 +- app/models/concerns/internal_id.rb | 1 - app/models/issue.rb | 4 +- app/models/merge_request.rb | 5 +- app/models/namespace.rb | 11 +- app/serializers/issue_entity.rb | 1 - app/services/groups/destroy_service.rb | 3 +- app/services/users/destroy_service.rb | 2 +- app/workers/group_destroy_worker.rb | 2 +- changelogs/unreleased/remove-soft-removals.yml | 5 + .../20171207150343_remove_soft_removed_objects.rb | 210 +++++++++++++++++++++ .../20171207150344_remove_deleted_at_columns.rb | 31 +++ db/schema.rb | 8 - doc/api/pipeline_triggers.md | 5 - lib/api/entities.rb | 2 +- lib/api/v3/entities.rb | 2 +- lib/gitlab/cycle_analytics/base_query.rb | 1 - lib/gitlab/hook_data/issue_builder.rb | 1 - lib/gitlab/hook_data/merge_request_builder.rb | 1 - spec/fixtures/api/schemas/entities/issue.json | 1 - .../api/schemas/entities/merge_request_widget.json | 1 - spec/javascripts/notes/mock_data.js | 2 - spec/javascripts/sidebar/mock_data.js | 2 - spec/javascripts/vue_mr_widget/mock_data.js | 1 - spec/lib/gitlab/hook_data/issue_builder_spec.rb | 1 - .../gitlab/hook_data/merge_request_builder_spec.rb | 1 - spec/lib/gitlab/import_export/project.group.json | 2 - spec/lib/gitlab/import_export/project.json | 20 -- spec/lib/gitlab/import_export/project.light.json | 1 - .../gitlab/import_export/safe_model_attributes.yml | 4 - spec/models/ci/pipeline_schedule_spec.rb | 1 - spec/models/issue_spec.rb | 5 - spec/models/merge_request_spec.rb | 5 - spec/models/namespace_spec.rb | 11 -- spec/services/users/destroy_service_spec.rb | 2 +- 38 files changed, 262 insertions(+), 105 deletions(-) create mode 100644 changelogs/unreleased/remove-soft-removals.yml create mode 100644 db/post_migrate/20171207150343_remove_soft_removed_objects.rb create mode 100644 db/post_migrate/20171207150344_remove_deleted_at_columns.rb diff --git a/Gemfile b/Gemfile index 38381d34b6b..da482ad3b7a 100644 --- a/Gemfile +++ b/Gemfile @@ -381,9 +381,6 @@ gem 'ruby-prof', '~> 0.16.2' # OAuth gem 'oauth2', '~> 1.4' -# Soft deletion -gem 'paranoia', '~> 2.3.1' - # Health check gem 'health_check', '~> 2.6.0' diff --git a/Gemfile.lock b/Gemfile.lock index 2a81c81b0f8..f35bccdf070 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -580,8 +580,6 @@ GEM orm_adapter (0.5.0) os (0.9.6) parallel (1.12.0) - paranoia (2.3.1) - activerecord (>= 4.0, < 5.2) parser (2.4.0.2) ast (~> 2.3) parslet (1.5.0) @@ -1110,7 +1108,6 @@ DEPENDENCIES omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) org-ruby (~> 0.9.12) - paranoia (~> 2.3.1) peek (~> 1.0.1) peek-gc (~> 0.0.2) peek-host (~> 1.0.0) diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index 10ead6b6d3b..b6abc3d7681 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -2,8 +2,9 @@ module Ci class PipelineSchedule < ActiveRecord::Base extend Gitlab::Ci::Model include Importable + include IgnorableColumn - acts_as_paranoid + ignore_column :deleted_at belongs_to :project belongs_to :owner, class_name: 'User' diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index b5290bcaf53..aa065e33739 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -1,8 +1,9 @@ module Ci class Trigger < ActiveRecord::Base extend Gitlab::Ci::Model + include IgnorableColumn - acts_as_paranoid + ignore_column :deleted_at belongs_to :project belongs_to :owner, class_name: "User" diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb index a3d0ac8d862..01079fb8bd6 100644 --- a/app/models/concerns/internal_id.rb +++ b/app/models/concerns/internal_id.rb @@ -10,7 +10,6 @@ module InternalId if iid.blank? parent = project || group records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend - records = records.with_deleted if self.paranoid? max_iid = records.maximum(:iid) self.iid = max_iid.to_i + 1 diff --git a/app/models/issue.rb b/app/models/issue.rb index ad4a3c737ff..93628b456f2 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base include ThrottledTouch include IgnorableColumn - ignore_column :assignee_id, :branch_name + ignore_column :assignee_id, :branch_name, :deleted_at DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze @@ -78,8 +78,6 @@ class Issue < ActiveRecord::Base end end - acts_as_paranoid - class << self alias_method :in_parents, :in_projects end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ef58816937c..8fdeddf1ed1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -11,7 +11,8 @@ class MergeRequest < ActiveRecord::Base include Gitlab::Utils::StrongMemoize ignore_column :locked_at, - :ref_fetched + :ref_fetched, + :deleted_at belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" @@ -150,8 +151,6 @@ class MergeRequest < ActiveRecord::Base after_save :keep_around_commit - acts_as_paranoid - def self.reference_prefix '!' end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index bdcc9159d26..37a7417cafc 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -1,6 +1,4 @@ class Namespace < ActiveRecord::Base - acts_as_paranoid without_default_scope: true - include CacheMarkdownField include Sortable include Gitlab::ShellAdapter @@ -10,6 +8,9 @@ class Namespace < ActiveRecord::Base include AfterCommitQueue include Storage::LegacyNamespace include Gitlab::SQL::Pattern + include IgnorableColumn + + ignore_column :deleted_at # Prevent users from creating unreasonably deep level of nesting. # The number 20 was taken based on maximum nesting level of @@ -221,12 +222,6 @@ class Namespace < ActiveRecord::Base has_parent? end - def soft_delete_without_removing_associations - # We can't use paranoia's `#destroy` since this will hard-delete projects. - # Project uses `pending_delete` instead of the acts_as_paranoia gem. - self.deleted_at = Time.now - end - private def refresh_access_of_projects_invited_groups diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index 0bdd4d7a272..b5e2334b6e3 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -6,7 +6,6 @@ class IssueEntity < IssuableEntity expose :updated_by_id expose :created_at expose :updated_at - expose :deleted_at expose :milestone, using: API::Entities::Milestone expose :labels, using: LabelEntity expose :lock_version diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index e3f9d9ee95d..58e88688dfa 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -1,7 +1,6 @@ module Groups class DestroyService < Groups::BaseService def async_execute - group.soft_delete_without_removing_associations 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}") end @@ -23,7 +22,7 @@ module Groups group.chat_team&.remove_mattermost_team(current_user) - group.really_destroy! + group.destroy end end end diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 00db8a2c434..b71002433d6 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -53,7 +53,7 @@ module Users # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing user_data = user.destroy - namespace.really_destroy! + namespace.destroy user_data end diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb index f577b310b20..509bd09dc2e 100644 --- a/app/workers/group_destroy_worker.rb +++ b/app/workers/group_destroy_worker.rb @@ -4,7 +4,7 @@ class GroupDestroyWorker def perform(group_id, user_id) begin - group = Group.with_deleted.find(group_id) + group = Group.find(group_id) rescue ActiveRecord::RecordNotFound return end diff --git a/changelogs/unreleased/remove-soft-removals.yml b/changelogs/unreleased/remove-soft-removals.yml new file mode 100644 index 00000000000..aa53d33e502 --- /dev/null +++ b/changelogs/unreleased/remove-soft-removals.yml @@ -0,0 +1,5 @@ +--- +title: Remove soft removals related code +merge_request: 15789 +author: +type: changed diff --git a/db/post_migrate/20171207150343_remove_soft_removed_objects.rb b/db/post_migrate/20171207150343_remove_soft_removed_objects.rb new file mode 100644 index 00000000000..542cfb42fdc --- /dev/null +++ b/db/post_migrate/20171207150343_remove_soft_removed_objects.rb @@ -0,0 +1,210 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveSoftRemovedObjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + module SoftRemoved + extend ActiveSupport::Concern + + included do + scope :soft_removed, -> { where('deleted_at IS NOT NULL') } + end + end + + class User < ActiveRecord::Base + self.table_name = 'users' + + include EachBatch + end + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + + include EachBatch + include SoftRemoved + end + + class MergeRequest < ActiveRecord::Base + self.table_name = 'merge_requests' + + include EachBatch + include SoftRemoved + end + + class Namespace < ActiveRecord::Base + self.table_name = 'namespaces' + + include EachBatch + include SoftRemoved + + scope :soft_removed_personal, -> { soft_removed.where(type: nil) } + scope :soft_removed_group, -> { soft_removed.where(type: 'Group') } + end + + class Route < ActiveRecord::Base + self.table_name = 'routes' + + include EachBatch + include SoftRemoved + end + + class Project < ActiveRecord::Base + self.table_name = 'projects' + + include EachBatch + include SoftRemoved + end + + class CiPipelineSchedule < ActiveRecord::Base + self.table_name = 'ci_pipeline_schedules' + + include EachBatch + include SoftRemoved + end + + class CiTrigger < ActiveRecord::Base + self.table_name = 'ci_triggers' + + include EachBatch + include SoftRemoved + end + + MODELS = [Issue, MergeRequest, CiPipelineSchedule, CiTrigger].freeze + + def up + disable_statement_timeout + + remove_personal_routes + remove_personal_namespaces + remove_group_namespaces + remove_simple_soft_removed_rows + end + + def down + # The data removed by this migration can't be restored in an automated way. + end + + def remove_simple_soft_removed_rows + create_temporary_indexes + + MODELS.each do |model| + say_with_time("Removing soft removed rows from #{model.table_name}") do + model.soft_removed.each_batch do |batch, index| + batch.delete_all + end + end + end + ensure + remove_temporary_indexes + end + + def create_temporary_indexes + MODELS.each do |model| + index_name = temporary_index_name_for(model) + + # Without this index the removal process can take a very long time. For + # example, getting the next ID of a batch for the `issues` table in + # staging would take between 15 and 20 seconds. + next if temporary_index_exists?(model) + + say_with_time("Creating temporary index #{index_name}") do + add_concurrent_index( + model.table_name, + [:deleted_at, :id], + name: index_name, + where: 'deleted_at IS NOT NULL' + ) + end + end + end + + def remove_temporary_indexes + MODELS.each do |model| + index_name = temporary_index_name_for(model) + + next unless temporary_index_exists?(model) + + say_with_time("Removing temporary index #{index_name}") do + remove_concurrent_index_by_name(model.table_name, index_name) + end + end + end + + def temporary_index_name_for(model) + "index_on_#{model.table_name}_tmp" + end + + def temporary_index_exists?(model) + index_name = temporary_index_name_for(model) + + index_exists?(model.table_name, [:deleted_at, :id], name: index_name) + end + + def remove_personal_namespaces + # Some personal namespaces are left behind in case of GitLab.com. In these + # cases the associated data such as the projects and users has already been + # removed. + Namespace.soft_removed_personal.each_batch do |batch| + batch.delete_all + end + end + + def remove_group_namespaces + # Left over groups can't be easily removed because we may also need to + # remove memberships, repositories, and other associated data. As a result + # we'll just schedule a Sidekiq job to remove these. + # + # As of January 5th, 2018 there are 36 groups that will be removed using + # this code. + Namespace.select(:id).soft_removed_group.each_batch(of: 10) do |batch, index| + # We need the ID of an admin user as the owners of the group may no longer + # exist (or might not even be set in `namespaces.owner_id`). + admin_id = id_for_admin_user + + batch.each do |ns| + schedule_group_removal(index * 5.minutes, ns.id, admin_id) + end + end + end + + def schedule_group_removal(delay, group_id, user_id) + if migrate_inline? + GroupDestroyWorker.new.perform(group_id, user_id) + else + GroupDestroyWorker.perform_in(delay, group_id, user_id) + end + end + + def remove_personal_routes + namespaces = Namespace.select(1) + .soft_removed + .where('namespaces.type IS NULL') + .where('routes.source_type = ?', 'Namespace') + .where('routes.source_id = namespaces.id') + + Route.where('EXISTS (?)', namespaces).each_batch do |batch| + batch.delete_all + end + end + + def id_for_admin_user + return @id_for_admin_user if @id_for_admin_user + + if (admin_id = User.where(admin: true).limit(1).pluck(:id).first) + @id_for_admin_user = admin_id + else + raise 'Can not remove soft removed groups as no admin user exists. ' \ + 'Please make sure at least one user with `admin` set to TRUE exists before proceeding.' + end + end + + def migrate_inline? + Rails.env.test? || Rails.env.development? + end +end diff --git a/db/post_migrate/20171207150344_remove_deleted_at_columns.rb b/db/post_migrate/20171207150344_remove_deleted_at_columns.rb new file mode 100644 index 00000000000..154d7a1b926 --- /dev/null +++ b/db/post_migrate/20171207150344_remove_deleted_at_columns.rb @@ -0,0 +1,31 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveDeletedAtColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + TABLES = %i[issues merge_requests namespaces ci_pipeline_schedules ci_triggers].freeze + COLUMN = :deleted_at + + def up + TABLES.each do |table| + remove_column(table, COLUMN) if column_exists?(table, COLUMN) + end + end + + def down + TABLES.each do |table| + unless column_exists?(table, COLUMN) + add_column(table, COLUMN, :datetime_with_timezone) + end + + unless index_exists?(table, COLUMN) + add_concurrent_index(table, COLUMN) + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e6a2ea4c862..544a1bcc439 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -356,7 +356,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.integer "project_id" t.integer "owner_id" t.boolean "active", default: true - t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" end @@ -466,7 +465,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do create_table "ci_triggers", force: :cascade do |t| t.string "token" - t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" @@ -860,7 +858,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.integer "iid" t.integer "updated_by_id" t.boolean "confidential", default: false, null: false - t.datetime "deleted_at" t.date "due_date" t.integer "moved_to_id" t.integer "lock_version" @@ -877,7 +874,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree - add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)", using: :btree @@ -1086,7 +1082,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.boolean "merge_when_pipeline_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" - t.datetime "deleted_at" t.string "in_progress_merge_commit_sha" t.integer "lock_version" t.text "title_html" @@ -1105,7 +1100,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree - add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree @@ -1165,7 +1159,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.boolean "share_with_group_lock", default: false t.integer "visibility_level", default: 20, null: false t.boolean "request_access_enabled", default: false, null: false - t.datetime "deleted_at" t.text "description_html" t.boolean "lfs_enabled" t.integer "parent_id" @@ -1175,7 +1168,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree - add_index "namespaces", ["deleted_at"], name: "index_namespaces_on_deleted_at", using: :btree add_index "namespaces", ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md index 9030ae32d17..e881e61d4ef 100644 --- a/doc/api/pipeline_triggers.md +++ b/doc/api/pipeline_triggers.md @@ -24,7 +24,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/ "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -55,7 +54,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/ "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -85,7 +83,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descri "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -116,7 +113,6 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descrip "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -146,7 +142,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", diff --git a/lib/api/entities.rb b/lib/api/entities.rb index bd0c54a1b04..4b3c26d7c02 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -918,7 +918,7 @@ module API class Trigger < Grape::Entity expose :id expose :token, :description - expose :created_at, :updated_at, :deleted_at, :last_used + expose :created_at, :updated_at, :last_used expose :owner, using: Entities::UserBasic end diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index c17b6f45ed8..64758dae7d3 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -207,7 +207,7 @@ module API end class Trigger < Grape::Entity - expose :token, :created_at, :updated_at, :deleted_at, :last_used + expose :token, :created_at, :updated_at, :last_used expose :owner, using: ::API::Entities::UserBasic end diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb index dcbdf9a64b0..8b3bc3e440d 100644 --- a/lib/gitlab/cycle_analytics/base_query.rb +++ b/lib/gitlab/cycle_analytics/base_query.rb @@ -15,7 +15,6 @@ module Gitlab query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])) .join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) .where(issue_table[:project_id].eq(@project.id)) # rubocop:disable Gitlab/ModuleWithInstanceVariables - .where(issue_table[:deleted_at].eq(nil)) .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables # Load merge_requests diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb index e29dd0d5b0e..f9b1a3caf5e 100644 --- a/lib/gitlab/hook_data/issue_builder.rb +++ b/lib/gitlab/hook_data/issue_builder.rb @@ -7,7 +7,6 @@ module Gitlab closed_at confidential created_at - deleted_at description due_date id diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb index ae9b68eb648..aff786864f2 100644 --- a/lib/gitlab/hook_data/merge_request_builder.rb +++ b/lib/gitlab/hook_data/merge_request_builder.rb @@ -5,7 +5,6 @@ module Gitlab assignee_id author_id created_at - deleted_at description head_pipeline_id id diff --git a/spec/fixtures/api/schemas/entities/issue.json b/spec/fixtures/api/schemas/entities/issue.json index 3d3329a3406..38467b4ca20 100644 --- a/spec/fixtures/api/schemas/entities/issue.json +++ b/spec/fixtures/api/schemas/entities/issue.json @@ -28,7 +28,6 @@ "confidential": { "type": "boolean" }, "discussion_locked": { "type": ["boolean", "null"] }, "updated_by_id": { "type": ["string", "null"] }, - "deleted_at": { "type": ["string", "null"] }, "time_estimate": { "type": "integer" }, "total_time_spent": { "type": "integer" }, "human_time_estimate": { "type": ["integer", "null"] }, diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 7f662098216..05461787f06 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -13,7 +13,6 @@ "updated_by_id": { "type": ["string", "null"] }, "created_at": { "type": "string" }, "updated_at": { "type": "string" }, - "deleted_at": { "type": ["string", "null"] }, "time_estimate": { "type": "integer" }, "total_time_spent": { "type": "integer" }, "human_time_estimate": { "type": ["integer", "null"] }, diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 6b608adff15..b020a1020df 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -29,7 +29,6 @@ export const noteableDataMock = { can_create_note: true, can_update: true, }, - deleted_at: null, description: '', due_date: null, human_time_estimate: null, @@ -283,7 +282,6 @@ export const loggedOutnoteableData = { "updated_by_id": 1, "created_at": "2017-02-07T10:11:18.395Z", "updated_at": "2017-08-08T10:22:51.564Z", - "deleted_at": null, "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index 3b094d20838..7bc591d2d47 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -15,7 +15,6 @@ const RESPONSE_MAP = { updated_by_id: 1, created_at: '2017-02-02T21: 49: 49.664Z', updated_at: '2017-05-03T22: 26: 03.760Z', - deleted_at: null, time_estimate: 0, total_time_spent: 0, human_time_estimate: null, @@ -153,7 +152,6 @@ const RESPONSE_MAP = { updated_by_id: 1, created_at: '2017-06-27T19:54:42.437Z', updated_at: '2017-08-18T03:39:49.222Z', - deleted_at: null, time_estimate: 0, total_time_spent: 0, human_time_estimate: null, diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index ca29c9fee32..ae494267659 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -14,7 +14,6 @@ export default { "updated_by_id": null, "created_at": "2017-04-07T12:27:26.718Z", "updated_at": "2017-04-07T15:39:25.852Z", - "deleted_at": null, "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index aeacd577d18..506b2c0be20 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -14,7 +14,6 @@ describe Gitlab::HookData::IssueBuilder do closed_at confidential created_at - deleted_at description due_date id diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index 78475403f9e..b61614e4790 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -12,7 +12,6 @@ describe Gitlab::HookData::MergeRequestBuilder do assignee_id author_id created_at - deleted_at description head_pipeline_id id diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json index 82a1fbd2fc5..1a561e81e4a 100644 --- a/spec/lib/gitlab/import_export/project.group.json +++ b/spec/lib/gitlab/import_export/project.group.json @@ -54,7 +54,6 @@ "iid": 1, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, @@ -134,7 +133,6 @@ "iid": 2, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 7580b62cfc0..4cf33778d15 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -56,7 +56,6 @@ "iid": 10, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "test_ee_field": "test", @@ -350,7 +349,6 @@ "iid": 9, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "milestone": { @@ -586,7 +584,6 @@ "iid": 8, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "label_links": [ @@ -820,7 +817,6 @@ "iid": 7, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1033,7 +1029,6 @@ "iid": 6, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1246,7 +1241,6 @@ "iid": 5, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1459,7 +1453,6 @@ "iid": 4, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1672,7 +1665,6 @@ "iid": 3, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1885,7 +1877,6 @@ "iid": 2, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -2098,7 +2089,6 @@ "iid": 1, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -2504,7 +2494,6 @@ "merge_when_pipeline_succeeds": true, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 671, @@ -2948,7 +2937,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 679, @@ -3228,7 +3216,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 777, @@ -3508,7 +3495,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 785, @@ -4198,7 +4184,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 793, @@ -4734,7 +4719,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 801, @@ -5223,7 +5207,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 809, @@ -5478,7 +5461,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 817, @@ -6168,7 +6150,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 825, @@ -6968,7 +6949,6 @@ "id": 123, "token": "cdbfasdf44a5958c83654733449e585", "project_id": 5, - "deleted_at": null, "created_at": "2017-01-16T15:25:28.637Z", "updated_at": "2017-01-16T15:25:28.637Z" } diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 02450478a77..5dbf0ed289b 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -54,7 +54,6 @@ "iid": 20, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ec577903eb5..c940fade6bd 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -14,7 +14,6 @@ Issue: - iid - updated_by_id - confidential -- deleted_at - closed_at - due_date - moved_to_id @@ -159,7 +158,6 @@ MergeRequest: - merge_when_pipeline_succeeds - merge_user_id - merge_commit_sha -- deleted_at - in_progress_merge_commit_sha - lock_version - milestone_id @@ -293,7 +291,6 @@ Ci::Trigger: - id - token - project_id -- deleted_at - created_at - updated_at - owner_id @@ -309,7 +306,6 @@ Ci::PipelineSchedule: - project_id - owner_id - active -- deleted_at - created_at - updated_at Clusters::Cluster: diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 9a278212efc..8ee15f0e734 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -12,7 +12,6 @@ describe Ci::PipelineSchedule do it { is_expected.to respond_to(:cron_timezone) } it { is_expected.to respond_to(:description) } it { is_expected.to respond_to(:next_run_at) } - it { is_expected.to respond_to(:deleted_at) } describe 'validations' do it 'does not allow invalid cron patters' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 5ced000cdb6..f5c9f551e65 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -18,11 +18,6 @@ describe Issue do subject { create(:issue) } - describe "act_as_paranoid" do - it { is_expected.to have_db_column(:deleted_at) } - it { is_expected.to have_db_index(:deleted_at) } - end - describe 'callbacks' do describe '#ensure_metrics' do it 'creates metrics after saving' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 07b3e1c1758..27e9c477d61 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -24,11 +24,6 @@ describe MergeRequest do it { is_expected.to include_module(Taskable) } end - describe "act_as_paranoid" do - it { is_expected.to have_db_column(:deleted_at) } - it { is_expected.to have_db_index(:deleted_at) } - end - describe 'validation' do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index b3f160f3119..c3673a0e2a3 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -410,17 +410,6 @@ describe Namespace do end end - describe '#soft_delete_without_removing_associations' do - let(:project1) { create(:project_empty_repo, namespace: namespace) } - - it 'updates the deleted_at timestamp but preserves projects' do - namespace.soft_delete_without_removing_associations - - expect(Project.all).to include(project1) - expect(namespace.deleted_at).not_to be_nil - end - end - describe '#user_ids_for_project_authorizations' do it 'returns the user IDs for which to refresh authorizations' do expect(namespace.user_ids_for_project_authorizations) diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index aeba9cd60bc..bb3d73edf8e 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -15,7 +15,7 @@ describe Users::DestroyService do expect { user_data['email'].to eq(user.email) } expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) - expect { Namespace.with_deleted.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound) + expect { Namespace.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound) end it 'will delete the project' do -- cgit v1.2.1 From b1378f05a11345f761cd31ae51aafc268003150a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 8 Jan 2018 17:33:51 -0200 Subject: Add rake task to check integrity of uploaded files --- lib/tasks/gitlab/uploads.rake | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/tasks/gitlab/uploads.rake diff --git a/lib/tasks/gitlab/uploads.rake b/lib/tasks/gitlab/uploads.rake new file mode 100644 index 00000000000..fd5ef78103b --- /dev/null +++ b/lib/tasks/gitlab/uploads.rake @@ -0,0 +1,44 @@ +namespace :gitlab do + namespace :uploads do + desc 'GitLab | Uploads | Check integrity of uploaded files' + task check: :environment do + puts 'Starting checking integrity of uploaded files' + + uploads_batches do |batch| + batch.each do |upload| + puts "- Checking file (#{upload.id}): #{upload.absolute_path}".color(:green) + + if upload.exist? + check_checksum(upload) + else + puts " * File does not exist on the file system".color(:red) + end + end + end + + puts 'Done!' + end + + def batch_size + ENV.fetch('BATCH', 200).to_i + end + + def calculate_checksum(absolute_path) + Digest::SHA256.file(absolute_path).hexdigest + end + + def check_checksum(upload) + checksum = calculate_checksum(upload.absolute_path) + + if checksum != upload.checksum + puts " * File checksum (#{checksum}) does not match the one in the database (#{upload.checksum})".color(:red) + end + end + + def uploads_batches(&block) + Upload.all.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches + yield relation + end + end + end +end -- cgit v1.2.1 From 4b1546159c29ef46f106bb1393a542187b950c4e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 8 Jan 2018 18:29:33 -0200 Subject: Add spec for gitlab:uploads rake tasks --- spec/tasks/gitlab/uploads_rake_spec.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 spec/tasks/gitlab/uploads_rake_spec.rb diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb new file mode 100644 index 00000000000..9330539f8c9 --- /dev/null +++ b/spec/tasks/gitlab/uploads_rake_spec.rb @@ -0,0 +1,28 @@ +require 'rake_helper' + +describe 'gitlab:uploads rake tasks' do + describe 'check' do + before do + Rake.application.rake_require 'tasks/gitlab/uploads' + end + + it 'outputs the integrity check for each uploaded file' do + uploaded_file = create(:upload) + + expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{uploaded_file.id}\): #{Regexp.quote(uploaded_file.absolute_path)}/).to_stdout + end + + it 'errors out about missing files on the file system' do + uploaded_file = create(:upload) + + expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout + end + + it 'errors out about invalid checksum' do + uploaded_file = create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) + uploaded_file.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e') + + expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{uploaded_file.checksum}\)/).to_stdout + end + end +end -- cgit v1.2.1 From 17c44f2bd836c7dc5e28c3bac1f24f8b90c27a8b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 8 Jan 2018 18:59:02 -0200 Subject: Add CHANGELOG --- changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml diff --git a/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml b/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml new file mode 100644 index 00000000000..5b850c92d17 --- /dev/null +++ b/changelogs/unreleased/da-verify-integrity-of-uploaded-files.yml @@ -0,0 +1,5 @@ +--- +title: Add rake task to check integrity of uploaded files +merge_request: +author: +type: added -- cgit v1.2.1 From e1008da2a81a1c77c63b4f28ea8cb2aa498bc372 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 8 Jan 2018 19:22:17 -0200 Subject: Add docs for the gitlab:uploads:check rake task --- doc/administration/raketasks/check.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index c39cb49b1c6..7e452ae503d 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -76,6 +76,26 @@ Example output: ![gitlab:user:check_repos output](../img/raketasks/check_repos_output.png) +## Uploaded Files Integrity + +The uploads check Rake task will loop through all uploads in the database +and runs two checks to determine the integrity of each file: + +1. Check if the file exist in the file system. +1. Check if the checksum of the file in the file system matches the checksum in the database. + +**Omnibus Installation** + +``` +sudo gitlab-rake gitlab:uploads:check +``` + +**Source Installation** + +```bash +sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production +``` + ## LDAP Check The LDAP check Rake task will test the bind_dn and password credentials -- cgit v1.2.1 From 45ec26536e96a0d663e7c6b2332a3671fcec1c69 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 8 Jan 2018 16:27:32 -0500 Subject: Refactor `explore:*` --- app/assets/javascripts/dispatcher.js | 751 +++++++++++---------- .../javascripts/pages/explore/groups/index.js | 14 + .../javascripts/pages/explore/projects/index.js | 5 + 3 files changed, 428 insertions(+), 342 deletions(-) create mode 100644 app/assets/javascripts/pages/explore/groups/index.js create mode 100644 app/assets/javascripts/pages/explore/projects/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 42f61d33f6e..87e225a88a0 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,97 +1,95 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ -import { s__ } from './locale'; -import projectSelect from './project_select'; -import IssuableIndex from './issuable_index'; -import Milestone from './milestone'; -import IssuableForm from './issuable_form'; -import LabelsSelect from './labels_select'; -import MilestoneSelect from './milestone_select'; -import NewBranchForm from './new_branch_form'; -import NotificationsForm from './notifications_form'; -import notificationsDropdown from './notifications_dropdown'; -import groupAvatar from './group_avatar'; -import GroupLabelSubscription from './group_label_subscription'; -import LineHighlighter from './line_highlighter'; -import BuildArtifacts from './build_artifacts'; -import CILintEditor from './ci_lint_editor'; -import groupsSelect from './groups_select'; -import Search from './search'; -import initAdmin from './admin'; -import NamespaceSelect from './namespace_select'; -import NewCommitForm from './new_commit_form'; -import Project from './project'; -import projectAvatar from './project_avatar'; -import MergeRequest from './merge_request'; -import Compare from './compare'; -import initCompareAutocomplete from './compare_autocomplete'; -import ProjectFindFile from './project_find_file'; -import ProjectNew from './project_new'; -import projectImport from './project_import'; -import Labels from './labels'; -import LabelManager from './label_manager'; -import Sidebar from './right_sidebar'; -import IssuableTemplateSelectors from './templates/issuable_template_selectors'; -import Flash from './flash'; -import CommitsList from './commits'; -import Issue from './issue'; -import BindInOut from './behaviors/bind_in_out'; -import SecretValues from './behaviors/secret_values'; -import DeleteModal from './branches/branches_delete_modal'; -import Group from './group'; -import GroupsList from './groups_list'; -import ProjectsList from './projects_list'; -import setupProjectEdit from './project_edit'; -import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; -import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; -import Landing from './landing'; -import BlobForkSuggestion from './blob/blob_fork_suggestion'; -import UserCallout from './user_callout'; -import ShortcutsWiki from './shortcuts_wiki'; -import Pipelines from './pipelines'; -import BlobViewer from './blob/viewer/index'; -import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; -import UsersSelect from './users_select'; -import RefSelectDropdown from './ref_select_dropdown'; -import GfmAutoComplete from './gfm_auto_complete'; -import ShortcutsBlob from './shortcuts_blob'; -import SigninTabsMemoizer from './signin_tabs_memoizer'; -import Star from './star'; -import Todos from './todos'; -import TreeView from './tree'; -import UsagePing from './usage_ping'; -import UsernameValidator from './username_validator'; -import VersionCheckImage from './version_check_image'; -import Wikis from './wikis'; -import ZenMode from './zen_mode'; -import initSettingsPanels from './settings_panels'; -import initExperimentalFlags from './experimental_flags'; -import OAuthRememberMe from './oauth_remember_me'; -import PerformanceBar from './performance_bar'; -import initBroadcastMessagesForm from './broadcast_message'; -import initNotes from './init_notes'; -import initLegacyFilters from './init_legacy_filters'; -import initIssuableSidebar from './init_issuable_sidebar'; -import initProjectVisibilitySelector from './project_visibility'; -import GpgBadges from './gpg_badges'; -import initChangesDropdown from './init_changes_dropdown'; -import NewGroupChild from './groups/new_group_child'; -import AbuseReports from './abuse_reports'; -import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; -import AjaxLoadingSpinner from './ajax_loading_spinner'; -import GlFieldErrors from './gl_field_errors'; -import GLForm from './gl_form'; -import Shortcuts from './shortcuts'; -import ShortcutsNavigation from './shortcuts_navigation'; -import ShortcutsFindFile from './shortcuts_find_file'; -import ShortcutsIssuable from './shortcuts_issuable'; -import U2FAuthenticate from './u2f/authenticate'; -import Members from './members'; -import memberExpirationDate from './member_expiration_date'; -import DueDateSelectors from './due_date_select'; -import Diff from './diff'; -import ProjectLabelSubscription from './project_label_subscription'; -import SearchAutocomplete from './search_autocomplete'; -import Activities from './activities'; +import { s__ } from "./locale"; +import projectSelect from "./project_select"; +import IssuableIndex from "./issuable_index"; +import Milestone from "./milestone"; +import IssuableForm from "./issuable_form"; +import LabelsSelect from "./labels_select"; +import MilestoneSelect from "./milestone_select"; +import NewBranchForm from "./new_branch_form"; +import NotificationsForm from "./notifications_form"; +import notificationsDropdown from "./notifications_dropdown"; +import groupAvatar from "./group_avatar"; +import GroupLabelSubscription from "./group_label_subscription"; +import LineHighlighter from "./line_highlighter"; +import BuildArtifacts from "./build_artifacts"; +import CILintEditor from "./ci_lint_editor"; +import groupsSelect from "./groups_select"; +import Search from "./search"; +import initAdmin from "./admin"; +import NamespaceSelect from "./namespace_select"; +import NewCommitForm from "./new_commit_form"; +import Project from "./project"; +import projectAvatar from "./project_avatar"; +import MergeRequest from "./merge_request"; +import Compare from "./compare"; +import initCompareAutocomplete from "./compare_autocomplete"; +import ProjectFindFile from "./project_find_file"; +import ProjectNew from "./project_new"; +import projectImport from "./project_import"; +import Labels from "./labels"; +import LabelManager from "./label_manager"; +import Sidebar from "./right_sidebar"; +import IssuableTemplateSelectors from "./templates/issuable_template_selectors"; +import Flash from "./flash"; +import CommitsList from "./commits"; +import Issue from "./issue"; +import BindInOut from "./behaviors/bind_in_out"; +import SecretValues from "./behaviors/secret_values"; +import DeleteModal from "./branches/branches_delete_modal"; +import Group from "./group"; +import ProjectsList from "./projects_list"; +import setupProjectEdit from "./project_edit"; +import MiniPipelineGraph from "./mini_pipeline_graph_dropdown"; +import BlobLinePermalinkUpdater from "./blob/blob_line_permalink_updater"; +import BlobForkSuggestion from "./blob/blob_fork_suggestion"; +import UserCallout from "./user_callout"; +import ShortcutsWiki from "./shortcuts_wiki"; +import Pipelines from "./pipelines"; +import BlobViewer from "./blob/viewer/index"; +import AutoWidthDropdownSelect from "./issuable/auto_width_dropdown_select"; +import UsersSelect from "./users_select"; +import RefSelectDropdown from "./ref_select_dropdown"; +import GfmAutoComplete from "./gfm_auto_complete"; +import ShortcutsBlob from "./shortcuts_blob"; +import SigninTabsMemoizer from "./signin_tabs_memoizer"; +import Star from "./star"; +import Todos from "./todos"; +import TreeView from "./tree"; +import UsagePing from "./usage_ping"; +import UsernameValidator from "./username_validator"; +import VersionCheckImage from "./version_check_image"; +import Wikis from "./wikis"; +import ZenMode from "./zen_mode"; +import initSettingsPanels from "./settings_panels"; +import initExperimentalFlags from "./experimental_flags"; +import OAuthRememberMe from "./oauth_remember_me"; +import PerformanceBar from "./performance_bar"; +import initBroadcastMessagesForm from "./broadcast_message"; +import initNotes from "./init_notes"; +import initLegacyFilters from "./init_legacy_filters"; +import initIssuableSidebar from "./init_issuable_sidebar"; +import initProjectVisibilitySelector from "./project_visibility"; +import GpgBadges from "./gpg_badges"; +import initChangesDropdown from "./init_changes_dropdown"; +import NewGroupChild from "./groups/new_group_child"; +import AbuseReports from "./abuse_reports"; +import { ajaxGet, convertPermissionToBoolean } from "./lib/utils/common_utils"; +import AjaxLoadingSpinner from "./ajax_loading_spinner"; +import GlFieldErrors from "./gl_field_errors"; +import GLForm from "./gl_form"; +import Shortcuts from "./shortcuts"; +import ShortcutsNavigation from "./shortcuts_navigation"; +import ShortcutsFindFile from "./shortcuts_find_file"; +import ShortcutsIssuable from "./shortcuts_issuable"; +import U2FAuthenticate from "./u2f/authenticate"; +import Members from "./members"; +import memberExpirationDate from "./member_expiration_date"; +import DueDateSelectors from "./due_date_select"; +import Diff from "./diff"; +import ProjectLabelSubscription from "./project_label_subscription"; +import SearchAutocomplete from "./search_autocomplete"; +import Activities from "./activities"; (function() { var Dispatcher; @@ -104,27 +102,34 @@ import Activities from './activities'; } Dispatcher.prototype.initPageScripts = function() { - var path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl; - const page = $('body').attr('data-page'); + var path, + shortcut_handler, + fileBlobPermalinkUrlElement, + fileBlobPermalinkUrl; + const page = $("body").attr("data-page"); if (!page) { return false; } - const fail = () => Flash('Error loading dynamic module'); + const fail = () => Flash("Error loading dynamic module"); - path = page.split(':'); + path = page.split(":"); shortcut_handler = null; - $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { - const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); - const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); + $(".js-gfm-input:not(.js-vue-textarea)").each((i, el) => { + const gfm = new GfmAutoComplete( + gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources + ); + const enableGFM = convertPermissionToBoolean( + el.dataset.supportsAutocomplete + ); gfm.setup($(el), { emojis: true, members: enableGFM, issues: enableGFM, milestones: enableGFM, mergeRequests: enableGFM, - labels: enableGFM, + labels: enableGFM }); }); @@ -132,287 +137,335 @@ import Activities from './activities'; new LineHighlighter(); new BlobLinePermalinkUpdater( - document.querySelector('#blob-content-holder'), - '.diff-line-num[data-line-number]', - document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'), + document.querySelector("#blob-content-holder"), + ".diff-line-num[data-line-number]", + document.querySelectorAll( + ".js-data-file-blob-permalink-url, .js-blob-blame-link" + ) ); shortcut_handler = new ShortcutsNavigation(); - fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); - fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); + fileBlobPermalinkUrlElement = document.querySelector( + ".js-data-file-blob-permalink-url" + ); + fileBlobPermalinkUrl = + fileBlobPermalinkUrlElement && + fileBlobPermalinkUrlElement.getAttribute("href"); new ShortcutsBlob({ skipResetBindings: true, - fileBlobPermalinkUrl, + fileBlobPermalinkUrl }); new BlobForkSuggestion({ - openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'), - forkButtons: document.querySelectorAll('.js-fork-suggestion-button'), - cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'), - suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'), - actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'), - }) - .init(); + openButtons: document.querySelectorAll( + ".js-edit-blob-link-fork-toggler" + ), + forkButtons: document.querySelectorAll(".js-fork-suggestion-button"), + cancelButtons: document.querySelectorAll( + ".js-cancel-fork-suggestion-button" + ), + suggestionSections: document.querySelectorAll( + ".js-file-fork-suggestion-section" + ), + actionTextPieces: document.querySelectorAll( + ".js-file-fork-suggestion-section-action" + ) + }).init(); } - const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); + const filteredSearchEnabled = + gl.FilteredSearchManager && document.querySelector(".filtered-search"); switch (page) { - case 'profiles:preferences:show': + case "profiles:preferences:show": initExperimentalFlags(); break; - case 'sessions:new': + case "sessions:new": new UsernameValidator(); new SigninTabsMemoizer(); - new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents(); + new OAuthRememberMe({ + container: $(".omniauth-container") + }).bindEvents(); break; - case 'projects:boards:show': - case 'projects:boards:index': + case "projects:boards:show": + case "projects:boards:index": shortcut_handler = new ShortcutsNavigation(); new UsersSelect(); break; - case 'projects:merge_requests:index': - case 'projects:issues:index': + case "projects:merge_requests:index": + case "projects:issues:index": if (filteredSearchEnabled) { - const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); + const filteredSearchManager = new gl.FilteredSearchManager( + page === "projects:issues:index" ? "issues" : "merge_requests" + ); filteredSearchManager.setup(); } - const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; + const pagePrefix = + page === "projects:merge_requests:index" + ? "merge_request_" + : "issue_"; new IssuableIndex(pagePrefix); shortcut_handler = new ShortcutsNavigation(); new UsersSelect(); break; - case 'projects:issues:show': + case "projects:issues:show": new Issue(); shortcut_handler = new ShortcutsIssuable(); new ZenMode(); initIssuableSidebar(); break; - case 'dashboard:milestones:index': + case "dashboard:milestones:index": projectSelect(); break; - case 'projects:milestones:show': - case 'groups:milestones:show': - case 'dashboard:milestones:show': + case "projects:milestones:show": + case "groups:milestones:show": + case "dashboard:milestones:show": new Milestone(); new Sidebar(); break; - case 'dashboard:issues': - case 'dashboard:merge_requests': + case "dashboard:issues": + case "dashboard:merge_requests": projectSelect(); initLegacyFilters(); break; - case 'groups:issues': - case 'groups:merge_requests': + case "groups:issues": + case "groups:merge_requests": if (filteredSearchEnabled) { - const filteredSearchManager = new gl.FilteredSearchManager(page === 'groups:issues' ? 'issues' : 'merge_requests'); + const filteredSearchManager = new gl.FilteredSearchManager( + page === "groups:issues" ? "issues" : "merge_requests" + ); filteredSearchManager.setup(); } projectSelect(); break; - case 'dashboard:todos:index': + case "dashboard:todos:index": new Todos(); break; - case 'dashboard:projects:index': - case 'dashboard:projects:starred': - case 'explore:projects:index': - case 'explore:projects:trending': - case 'explore:projects:starred': - case 'admin:projects:index': + case "explore:projects:index": + case "explore:projects:trending": + case "explore:projects:starred": + import("./pages/explore/projects") + .then(module => module.default()) + .catch(fail); + break; + case "dashboard:projects:index": + case "dashboard:projects:starred": + case "admin:projects:index": new ProjectsList(); break; - case 'explore:groups:index': - new GroupsList(); - const landingElement = document.querySelector('.js-explore-groups-landing'); - if (!landingElement) break; - const exploreGroupsLanding = new Landing( - landingElement, - landingElement.querySelector('.dismiss-button'), - 'explore_groups_landing_dismissed', - ); - exploreGroupsLanding.toggle(); + case "explore:groups:index": + import("./pages/explore/groups") + .then(module => module.default()) + .catch(fail); break; - case 'projects:milestones:new': - case 'projects:milestones:edit': - case 'projects:milestones:update': + case "projects:milestones:new": + case "projects:milestones:edit": + case "projects:milestones:update": new ZenMode(); new DueDateSelectors(); - new GLForm($('.milestone-form'), true); + new GLForm($(".milestone-form"), true); break; - case 'groups:milestones:new': - case 'groups:milestones:edit': - case 'groups:milestones:update': + case "groups:milestones:new": + case "groups:milestones:edit": + case "groups:milestones:update": new ZenMode(); new DueDateSelectors(); - new GLForm($('.milestone-form'), false); + new GLForm($(".milestone-form"), false); break; - case 'projects:compare:show': + case "projects:compare:show": new Diff(); const paddingTop = 16; - initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop); + initChangesDropdown( + document.querySelector(".navbar-gitlab").offsetHeight - paddingTop + ); break; - case 'projects:branches:new': - case 'projects:branches:create': - new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)); + case "projects:branches:new": + case "projects:branches:create": + new NewBranchForm( + $(".js-create-branch-form"), + JSON.parse(document.getElementById("availableRefs").innerHTML) + ); break; - case 'projects:branches:index': + case "projects:branches:index": AjaxLoadingSpinner.init(); new DeleteModal(); break; - case 'projects:issues:new': - case 'projects:issues:edit': + case "projects:issues:new": + case "projects:issues:edit": shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.issue-form'), true); - new IssuableForm($('.issue-form')); + new GLForm($(".issue-form"), true); + new IssuableForm($(".issue-form")); new LabelsSelect(); new MilestoneSelect(); new IssuableTemplateSelectors(); break; - case 'projects:merge_requests:creations:new': - const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); + case "projects:merge_requests:creations:new": + const mrNewCompareNode = document.querySelector( + ".js-merge-request-new-compare" + ); if (mrNewCompareNode) { new Compare({ targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl, sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl, - targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl, + targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl }); } else { - const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit'); + const mrNewSubmitNode = document.querySelector( + ".js-merge-request-new-submit" + ); new MergeRequest({ - action: mrNewSubmitNode.dataset.mrSubmitAction, + action: mrNewSubmitNode.dataset.mrSubmitAction }); } - case 'projects:merge_requests:creations:diffs': - case 'projects:merge_requests:edit': + case "projects:merge_requests:creations:diffs": + case "projects:merge_requests:edit": new Diff(); shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.merge-request-form'), true); - new IssuableForm($('.merge-request-form')); + new GLForm($(".merge-request-form"), true); + new IssuableForm($(".merge-request-form")); new LabelsSelect(); new MilestoneSelect(); new IssuableTemplateSelectors(); - new AutoWidthDropdownSelect($('.js-target-branch-select')).init(); + new AutoWidthDropdownSelect($(".js-target-branch-select")).init(); break; - case 'projects:tags:new': + case "projects:tags:new": new ZenMode(); - new GLForm($('.tag-form'), true); - new RefSelectDropdown($('.js-branch-select')); + new GLForm($(".tag-form"), true); + new RefSelectDropdown($(".js-branch-select")); break; - case 'projects:snippets:show': + case "projects:snippets:show": initNotes(); new ZenMode(); break; - case 'projects:snippets:new': - case 'projects:snippets:edit': - case 'projects:snippets:create': - case 'projects:snippets:update': - new GLForm($('.snippet-form'), true); + case "projects:snippets:new": + case "projects:snippets:edit": + case "projects:snippets:create": + case "projects:snippets:update": + new GLForm($(".snippet-form"), true); new ZenMode(); break; - case 'snippets:new': - case 'snippets:edit': - case 'snippets:create': - case 'snippets:update': - new GLForm($('.snippet-form'), false); + case "snippets:new": + case "snippets:edit": + case "snippets:create": + case "snippets:update": + new GLForm($(".snippet-form"), false); new ZenMode(); break; - case 'projects:releases:edit': + case "projects:releases:edit": new ZenMode(); - new GLForm($('.release-form'), true); + new GLForm($(".release-form"), true); break; - case 'projects:merge_requests:show': + case "projects:merge_requests:show": new Diff(); new ZenMode(); initIssuableSidebar(); initNotes(); - const mrShowNode = document.querySelector('.merge-request'); + const mrShowNode = document.querySelector(".merge-request"); window.mergeRequest = new MergeRequest({ - action: mrShowNode.dataset.mrAction, + action: mrShowNode.dataset.mrAction }); shortcut_handler = new ShortcutsIssuable(true); break; - case 'dashboard:activity': + case "dashboard:activity": new Activities(); break; - case 'projects:commit:show': + case "projects:commit:show": new Diff(); new ZenMode(); shortcut_handler = new ShortcutsNavigation(); new MiniPipelineGraph({ - container: '.js-commit-pipeline-graph', + container: ".js-commit-pipeline-graph" }).bindEvents(); initNotes(); const stickyBarPaddingTop = 16; - initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop); - $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); + initChangesDropdown( + document.querySelector(".navbar-gitlab").offsetHeight - + stickyBarPaddingTop + ); + $(".commit-info.branches").load( + document.querySelector(".js-commit-box").dataset.commitPath + ); break; - case 'projects:commit:pipelines': + case "projects:commit:pipelines": new MiniPipelineGraph({ - container: '.js-commit-pipeline-graph', + container: ".js-commit-pipeline-graph" }).bindEvents(); - $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); + $(".commit-info.branches").load( + document.querySelector(".js-commit-box").dataset.commitPath + ); break; - case 'projects:activity': + case "projects:activity": new Activities(); shortcut_handler = new ShortcutsNavigation(); break; - case 'projects:commits:show': - CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); + case "projects:commits:show": + CommitsList.init( + document.querySelector(".js-project-commits-show").dataset + .commitsLimit + ); shortcut_handler = new ShortcutsNavigation(); GpgBadges.fetch(); break; - case 'projects:show': + case "projects:show": shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); new UserCallout({ setCalloutPerProject: true, - className: 'js-autodevops-banner', + className: "js-autodevops-banner" }); - if ($('#tree-slider').length) new TreeView(); - if ($('.blob-viewer').length) new BlobViewer(); - if ($('.project-show-activity').length) new Activities(); - $('#tree-slider').waitForImages(function() { - ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); + if ($("#tree-slider").length) new TreeView(); + if ($(".blob-viewer").length) new BlobViewer(); + if ($(".project-show-activity").length) new Activities(); + $("#tree-slider").waitForImages(function() { + ajaxGet( + document.querySelector(".js-tree-content").dataset.logsPath + ); }); break; - case 'projects:edit': + case "projects:edit": setupProjectEdit(); // Initialize expandable settings panels initSettingsPanels(); break; - case 'projects:imports:show': + case "projects:imports:show": projectImport(); break; - case 'projects:pipelines:new': - case 'projects:pipelines:create': - new NewBranchForm($('.js-new-pipeline-form')); - break; - case 'projects:pipelines:builds': - case 'projects:pipelines:failures': - case 'projects:pipelines:show': - const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; - const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`; + case "projects:pipelines:new": + case "projects:pipelines:create": + new NewBranchForm($(".js-new-pipeline-form")); + break; + case "projects:pipelines:builds": + case "projects:pipelines:failures": + case "projects:pipelines:show": + const { controllerAction } = document.querySelector( + ".js-pipeline-container" + ).dataset; + const pipelineStatusUrl = `${document + .querySelector(".js-pipeline-tab-link a") + .getAttribute("href")}/status.json`; new Pipelines({ initTabs: true, pipelineStatusUrl, tabsOptions: { action: controllerAction, - defaultAction: 'pipelines', - parentEl: '.pipelines-tabs', - }, + defaultAction: "pipelines", + parentEl: ".pipelines-tabs" + } }); break; - case 'groups:activity': + case "groups:activity": new Activities(); break; - case 'groups:show': - const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup'); + case "groups:show": + const newGroupChildWrapper = document.querySelector( + ".js-new-project-subgroup" + ); shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); notificationsDropdown(); @@ -422,259 +475,273 @@ import Activities from './activities'; new NewGroupChild(newGroupChildWrapper); } break; - case 'groups:group_members:index': + case "groups:group_members:index": memberExpirationDate(); new Members(); new UsersSelect(); break; - case 'projects:project_members:index': - memberExpirationDate('.js-access-expiration-date-groups'); + case "projects:project_members:index": + memberExpirationDate(".js-access-expiration-date-groups"); groupsSelect(); memberExpirationDate(); new Members(); new UsersSelect(); break; - case 'groups:new': - case 'admin:groups:new': - case 'groups:create': - case 'admin:groups:create': + case "groups:new": + case "admin:groups:new": + case "groups:create": + case "admin:groups:create": BindInOut.initAll(); new Group(); groupAvatar(); break; - case 'groups:edit': - case 'admin:groups:edit': + case "groups:edit": + case "admin:groups:edit": groupAvatar(); break; - case 'projects:tree:show': + case "projects:tree:show": shortcut_handler = new ShortcutsNavigation(); new TreeView(); new BlobViewer(); - new NewCommitForm($('.js-create-dir-form')); - $('#tree-slider').waitForImages(function() { - ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); + new NewCommitForm($(".js-create-dir-form")); + $("#tree-slider").waitForImages(function() { + ajaxGet( + document.querySelector(".js-tree-content").dataset.logsPath + ); }); break; - case 'projects:find_file:show': - const findElement = document.querySelector('.js-file-finder'); - const projectFindFile = new ProjectFindFile($(".file-finder-holder"), { - url: findElement.dataset.fileFindUrl, - treeUrl: findElement.dataset.findTreeUrl, - blobUrlTemplate: findElement.dataset.blobUrlTemplate, - }); + case "projects:find_file:show": + const findElement = document.querySelector(".js-file-finder"); + const projectFindFile = new ProjectFindFile( + $(".file-finder-holder"), + { + url: findElement.dataset.fileFindUrl, + treeUrl: findElement.dataset.findTreeUrl, + blobUrlTemplate: findElement.dataset.blobUrlTemplate + } + ); new ShortcutsFindFile(projectFindFile); shortcut_handler = true; break; - case 'projects:blob:show': + case "projects:blob:show": new BlobViewer(); initBlob(); break; - case 'projects:blame:show': + case "projects:blame:show": initBlob(); break; - case 'groups:labels:new': - case 'groups:labels:edit': - case 'projects:labels:new': - case 'projects:labels:edit': + case "groups:labels:new": + case "groups:labels:edit": + case "projects:labels:new": + case "projects:labels:edit": new Labels(); break; - case 'groups:labels:index': - case 'projects:labels:index': - if ($('.prioritized-labels').length) { + case "groups:labels:index": + case "projects:labels:index": + if ($(".prioritized-labels").length) { new LabelManager(); } - $('.label-subscription').each((i, el) => { + $(".label-subscription").each((i, el) => { const $el = $(el); - if ($el.find('.dropdown-group-label').length) { + if ($el.find(".dropdown-group-label").length) { new GroupLabelSubscription($el); } else { new ProjectLabelSubscription($el); } }); break; - case 'projects:network:show': + case "projects:network:show": // Ensure we don't create a particular shortcut handler here. This is // already created, where the network graph is created. shortcut_handler = true; break; - case 'projects:forks:new': - import(/* webpackChunkName: 'project_fork' */ './project_fork') + case "projects:forks:new": + import(/* webpackChunkName: 'project_fork' */ "./project_fork") .then(fork => fork.default()) .catch(() => {}); break; - case 'projects:artifacts:browse': + case "projects:artifacts:browse": new ShortcutsNavigation(); new BuildArtifacts(); break; - case 'projects:artifacts:file': + case "projects:artifacts:file": new ShortcutsNavigation(); new BlobViewer(); break; - case 'help:index': - VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); + case "help:index": + VersionCheckImage.bindErrorEvent($("img.js-version-status-badge")); break; - case 'search:show': + case "search:show": new Search(); break; - case 'projects:settings:repository:show': + case "projects:settings:repository:show": // Initialize expandable settings panels initSettingsPanels(); break; - case 'projects:settings:ci_cd:show': + case "projects:settings:ci_cd:show": // Initialize expandable settings panels initSettingsPanels(); - const runnerToken = document.querySelector('.js-secret-runner-token'); + const runnerToken = document.querySelector(".js-secret-runner-token"); if (runnerToken) { const runnerTokenSecretValue = new SecretValues(runnerToken); runnerTokenSecretValue.init(); } - case 'groups:settings:ci_cd:show': - const secretVariableTable = document.querySelector('.js-secret-variable-table'); + case "groups:settings:ci_cd:show": + const secretVariableTable = document.querySelector( + ".js-secret-variable-table" + ); if (secretVariableTable) { - const secretVariableTableValues = new SecretValues(secretVariableTable); + const secretVariableTableValues = new SecretValues( + secretVariableTable + ); secretVariableTableValues.init(); } break; - case 'ci:lints:create': - case 'ci:lints:show': + case "ci:lints:create": + case "ci:lints:show": new CILintEditor(); break; - case 'users:show': - import('./pages/users/show').then(m => m.default()).catch(fail); + case "users:show": + import("./pages/users/show") + .then(m => m.default()) + .catch(fail); break; - case 'admin:conversational_development_index:show': + case "admin:conversational_development_index:show": new UserCallout(); break; - case 'snippets:show': + case "snippets:show": new LineHighlighter(); new BlobViewer(); initNotes(); new ZenMode(); break; - case 'import:fogbugz:new_user_map': + case "import:fogbugz:new_user_map": new UsersSelect(); break; - case 'profiles:personal_access_tokens:index': - case 'admin:impersonation_tokens:index': + case "profiles:personal_access_tokens:index": + case "admin:impersonation_tokens:index": new DueDateSelectors(); break; - case 'projects:clusters:show': - import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle') + case "projects:clusters:show": + import(/* webpackChunkName: "clusters" */ "./clusters/clusters_bundle") .then(cluster => new cluster.default()) // eslint-disable-line new-cap - .catch((err) => { - Flash(s__('ClusterIntegration|Problem setting up the cluster')); + .catch(err => { + Flash(s__("ClusterIntegration|Problem setting up the cluster")); throw err; }); break; - case 'projects:clusters:index': - import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index') + case "projects:clusters:index": + import(/* webpackChunkName: "clusters_index" */ "./clusters/clusters_index") .then(clusterIndex => clusterIndex.default()) - .catch((err) => { - Flash(s__('ClusterIntegration|Problem setting up the clusters list')); + .catch(err => { + Flash( + s__("ClusterIntegration|Problem setting up the clusters list") + ); throw err; }); break; } switch (path[0]) { - case 'sessions': - case 'omniauth_callbacks': + case "sessions": + case "omniauth_callbacks": if (!gon.u2f) break; const u2fAuthenticate = new U2FAuthenticate( - $('#js-authenticate-u2f'), - '#js-login-u2f-form', + $("#js-authenticate-u2f"), + "#js-login-u2f-form", gon.u2f, - document.querySelector('#js-login-2fa-device'), - document.querySelector('.js-2fa-form'), + document.querySelector("#js-login-2fa-device"), + document.querySelector(".js-2fa-form") ); u2fAuthenticate.start(); // needed in rspec gl.u2fAuthenticate = u2fAuthenticate; - case 'admin': + case "admin": initAdmin(); switch (path[1]) { - case 'broadcast_messages': + case "broadcast_messages": initBroadcastMessagesForm(); break; - case 'cohorts': + case "cohorts": new UsagePing(); break; - case 'groups': + case "groups": new UsersSelect(); break; - case 'projects': - document.querySelectorAll('.js-namespace-select') + case "projects": + document + .querySelectorAll(".js-namespace-select") .forEach(dropdown => new NamespaceSelect({ dropdown })); break; - case 'labels': + case "labels": switch (path[2]) { - case 'new': - case 'edit': + case "new": + case "edit": new Labels(); } - case 'abuse_reports': + case "abuse_reports": new AbuseReports(); break; } break; - case 'dashboard': - case 'root': + case "dashboard": + case "root": new UserCallout(); break; - case 'profiles': + case "profiles": new NotificationsForm(); notificationsDropdown(); break; - case 'projects': + case "projects": new Project(); projectAvatar(); switch (path[1]) { - case 'compare': + case "compare": initCompareAutocomplete(); break; - case 'edit': + case "edit": shortcut_handler = new ShortcutsNavigation(); new ProjectNew(); - import(/* webpackChunkName: 'project_permissions' */ './projects/permissions') + import(/* webpackChunkName: 'project_permissions' */ "./projects/permissions") .then(permissions => permissions.default()) .catch(() => {}); break; - case 'new': + case "new": new ProjectNew(); initProjectVisibilitySelector(); break; - case 'show': + case "show": new Star(); new ProjectNew(); notificationsDropdown(); break; - case 'wikis': + case "wikis": new Wikis(); shortcut_handler = new ShortcutsWiki(); new ZenMode(); - new GLForm($('.wiki-form'), true); + new GLForm($(".wiki-form"), true); break; - case 'snippets': + case "snippets": shortcut_handler = new ShortcutsNavigation(); - if (path[2] === 'show') { + if (path[2] === "show") { new ZenMode(); new LineHighlighter(); new BlobViewer(); } break; - case 'labels': - case 'graphs': - case 'compare': - case 'pipelines': - case 'forks': - case 'milestones': - case 'project_members': - case 'deploy_keys': - case 'builds': - case 'hooks': - case 'services': - case 'protected_branches': + case "labels": + case "graphs": + case "compare": + case "pipelines": + case "forks": + case "milestones": + case "project_members": + case "deploy_keys": + case "builds": + case "hooks": + case "services": + case "protected_branches": shortcut_handler = new ShortcutsNavigation(); } break; @@ -684,20 +751,20 @@ import Activities from './activities'; new Shortcuts(); } - if (document.querySelector('#peek')) { - new PerformanceBar({ container: '#peek' }); + if (document.querySelector("#peek")) { + new PerformanceBar({ container: "#peek" }); } }; Dispatcher.prototype.initSearch = function() { // Only when search form is present - if ($('.search').length) { + if ($(".search").length) { return new SearchAutocomplete(); } }; Dispatcher.prototype.initFieldErrors = function() { - $('.gl-show-field-errors').each((i, form) => { + $(".gl-show-field-errors").each((i, form) => { new GlFieldErrors(form); }); }; @@ -705,7 +772,7 @@ import Activities from './activities'; return Dispatcher; })(); - $(window).on('load', function() { + $(window).on("load", function() { new Dispatcher(); }); -}).call(window); +}.call(window)); diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js new file mode 100644 index 00000000000..97c4e2dc869 --- /dev/null +++ b/app/assets/javascripts/pages/explore/groups/index.js @@ -0,0 +1,14 @@ +import GroupsList from "~/groups_list"; +import Landing from "~/landing"; + +export default function() { + new GroupsList(); + const landingElement = document.querySelector(".js-explore-groups-landing"); + if (!landingElement) return; + const exploreGroupsLanding = new Landing( + landingElement, + landingElement.querySelector(".dismiss-button"), + "explore_groups_landing_dismissed" + ); + exploreGroupsLanding.toggle(); +} diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js new file mode 100644 index 00000000000..0bbb82b9daf --- /dev/null +++ b/app/assets/javascripts/pages/explore/projects/index.js @@ -0,0 +1,5 @@ +import ProjectsList from "~/projects_list"; + +export default function() { + new ProjectsList(); +} -- cgit v1.2.1 From 1aff48068074409b7cc072f484638d8be1cdcb7c Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 8 Jan 2018 16:38:06 -0500 Subject: Updating dispatcher without code fixes --- app/assets/javascripts/dispatcher.js | 755 ++++++++++----------- .../javascripts/pages/explore/groups/index.js | 10 +- .../javascripts/pages/explore/projects/index.js | 2 +- 3 files changed, 357 insertions(+), 410 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 87e225a88a0..af516a8e7ff 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,95 +1,97 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ -import { s__ } from "./locale"; -import projectSelect from "./project_select"; -import IssuableIndex from "./issuable_index"; -import Milestone from "./milestone"; -import IssuableForm from "./issuable_form"; -import LabelsSelect from "./labels_select"; -import MilestoneSelect from "./milestone_select"; -import NewBranchForm from "./new_branch_form"; -import NotificationsForm from "./notifications_form"; -import notificationsDropdown from "./notifications_dropdown"; -import groupAvatar from "./group_avatar"; -import GroupLabelSubscription from "./group_label_subscription"; -import LineHighlighter from "./line_highlighter"; -import BuildArtifacts from "./build_artifacts"; -import CILintEditor from "./ci_lint_editor"; -import groupsSelect from "./groups_select"; -import Search from "./search"; -import initAdmin from "./admin"; -import NamespaceSelect from "./namespace_select"; -import NewCommitForm from "./new_commit_form"; -import Project from "./project"; -import projectAvatar from "./project_avatar"; -import MergeRequest from "./merge_request"; -import Compare from "./compare"; -import initCompareAutocomplete from "./compare_autocomplete"; -import ProjectFindFile from "./project_find_file"; -import ProjectNew from "./project_new"; -import projectImport from "./project_import"; -import Labels from "./labels"; -import LabelManager from "./label_manager"; -import Sidebar from "./right_sidebar"; -import IssuableTemplateSelectors from "./templates/issuable_template_selectors"; -import Flash from "./flash"; -import CommitsList from "./commits"; -import Issue from "./issue"; -import BindInOut from "./behaviors/bind_in_out"; -import SecretValues from "./behaviors/secret_values"; -import DeleteModal from "./branches/branches_delete_modal"; -import Group from "./group"; -import ProjectsList from "./projects_list"; -import setupProjectEdit from "./project_edit"; -import MiniPipelineGraph from "./mini_pipeline_graph_dropdown"; -import BlobLinePermalinkUpdater from "./blob/blob_line_permalink_updater"; -import BlobForkSuggestion from "./blob/blob_fork_suggestion"; -import UserCallout from "./user_callout"; -import ShortcutsWiki from "./shortcuts_wiki"; -import Pipelines from "./pipelines"; -import BlobViewer from "./blob/viewer/index"; -import AutoWidthDropdownSelect from "./issuable/auto_width_dropdown_select"; -import UsersSelect from "./users_select"; -import RefSelectDropdown from "./ref_select_dropdown"; -import GfmAutoComplete from "./gfm_auto_complete"; -import ShortcutsBlob from "./shortcuts_blob"; -import SigninTabsMemoizer from "./signin_tabs_memoizer"; -import Star from "./star"; -import Todos from "./todos"; -import TreeView from "./tree"; -import UsagePing from "./usage_ping"; -import UsernameValidator from "./username_validator"; -import VersionCheckImage from "./version_check_image"; -import Wikis from "./wikis"; -import ZenMode from "./zen_mode"; -import initSettingsPanels from "./settings_panels"; -import initExperimentalFlags from "./experimental_flags"; -import OAuthRememberMe from "./oauth_remember_me"; -import PerformanceBar from "./performance_bar"; -import initBroadcastMessagesForm from "./broadcast_message"; -import initNotes from "./init_notes"; -import initLegacyFilters from "./init_legacy_filters"; -import initIssuableSidebar from "./init_issuable_sidebar"; -import initProjectVisibilitySelector from "./project_visibility"; -import GpgBadges from "./gpg_badges"; -import initChangesDropdown from "./init_changes_dropdown"; -import NewGroupChild from "./groups/new_group_child"; -import AbuseReports from "./abuse_reports"; -import { ajaxGet, convertPermissionToBoolean } from "./lib/utils/common_utils"; -import AjaxLoadingSpinner from "./ajax_loading_spinner"; -import GlFieldErrors from "./gl_field_errors"; -import GLForm from "./gl_form"; -import Shortcuts from "./shortcuts"; -import ShortcutsNavigation from "./shortcuts_navigation"; -import ShortcutsFindFile from "./shortcuts_find_file"; -import ShortcutsIssuable from "./shortcuts_issuable"; -import U2FAuthenticate from "./u2f/authenticate"; -import Members from "./members"; -import memberExpirationDate from "./member_expiration_date"; -import DueDateSelectors from "./due_date_select"; -import Diff from "./diff"; -import ProjectLabelSubscription from "./project_label_subscription"; -import SearchAutocomplete from "./search_autocomplete"; -import Activities from "./activities"; +import { s__ } from './locale'; +import projectSelect from './project_select'; +import IssuableIndex from './issuable_index'; +import Milestone from './milestone'; +import IssuableForm from './issuable_form'; +import LabelsSelect from './labels_select'; +import MilestoneSelect from './milestone_select'; +import NewBranchForm from './new_branch_form'; +import NotificationsForm from './notifications_form'; +import notificationsDropdown from './notifications_dropdown'; +import groupAvatar from './group_avatar'; +import GroupLabelSubscription from './group_label_subscription'; +import LineHighlighter from './line_highlighter'; +import BuildArtifacts from './build_artifacts'; +import CILintEditor from './ci_lint_editor'; +import groupsSelect from './groups_select'; +import Search from './search'; +import initAdmin from './admin'; +import NamespaceSelect from './namespace_select'; +import NewCommitForm from './new_commit_form'; +import Project from './project'; +import projectAvatar from './project_avatar'; +import MergeRequest from './merge_request'; +import Compare from './compare'; +import initCompareAutocomplete from './compare_autocomplete'; +import ProjectFindFile from './project_find_file'; +import ProjectNew from './project_new'; +import projectImport from './project_import'; +import Labels from './labels'; +import LabelManager from './label_manager'; +import Sidebar from './right_sidebar'; +import IssuableTemplateSelectors from './templates/issuable_template_selectors'; +import Flash from './flash'; +import CommitsList from './commits'; +import Issue from './issue'; +import BindInOut from './behaviors/bind_in_out'; +import SecretValues from './behaviors/secret_values'; +import DeleteModal from './branches/branches_delete_modal'; +import Group from './group'; +import GroupsList from './groups_list'; +import ProjectsList from './projects_list'; +import setupProjectEdit from './project_edit'; +import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; +import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; +import Landing from './landing'; +import BlobForkSuggestion from './blob/blob_fork_suggestion'; +import UserCallout from './user_callout'; +import ShortcutsWiki from './shortcuts_wiki'; +import Pipelines from './pipelines'; +import BlobViewer from './blob/viewer/index'; +import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; +import UsersSelect from './users_select'; +import RefSelectDropdown from './ref_select_dropdown'; +import GfmAutoComplete from './gfm_auto_complete'; +import ShortcutsBlob from './shortcuts_blob'; +import SigninTabsMemoizer from './signin_tabs_memoizer'; +import Star from './star'; +import Todos from './todos'; +import TreeView from './tree'; +import UsagePing from './usage_ping'; +import UsernameValidator from './username_validator'; +import VersionCheckImage from './version_check_image'; +import Wikis from './wikis'; +import ZenMode from './zen_mode'; +import initSettingsPanels from './settings_panels'; +import initExperimentalFlags from './experimental_flags'; +import OAuthRememberMe from './oauth_remember_me'; +import PerformanceBar from './performance_bar'; +import initBroadcastMessagesForm from './broadcast_message'; +import initNotes from './init_notes'; +import initLegacyFilters from './init_legacy_filters'; +import initIssuableSidebar from './init_issuable_sidebar'; +import initProjectVisibilitySelector from './project_visibility'; +import GpgBadges from './gpg_badges'; +import initChangesDropdown from './init_changes_dropdown'; +import NewGroupChild from './groups/new_group_child'; +import AbuseReports from './abuse_reports'; +import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; +import AjaxLoadingSpinner from './ajax_loading_spinner'; +import GlFieldErrors from './gl_field_errors'; +import GLForm from './gl_form'; +import Shortcuts from './shortcuts'; +import ShortcutsNavigation from './shortcuts_navigation'; +import ShortcutsFindFile from './shortcuts_find_file'; +import ShortcutsIssuable from './shortcuts_issuable'; +import U2FAuthenticate from './u2f/authenticate'; +import Members from './members'; +import memberExpirationDate from './member_expiration_date'; +import DueDateSelectors from './due_date_select'; +import Diff from './diff'; +import ProjectLabelSubscription from './project_label_subscription'; +import SearchAutocomplete from './search_autocomplete'; +import Activities from './activities'; (function() { var Dispatcher; @@ -102,34 +104,27 @@ import Activities from "./activities"; } Dispatcher.prototype.initPageScripts = function() { - var path, - shortcut_handler, - fileBlobPermalinkUrlElement, - fileBlobPermalinkUrl; - const page = $("body").attr("data-page"); + var path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl; + const page = $('body').attr('data-page'); if (!page) { return false; } - const fail = () => Flash("Error loading dynamic module"); + const fail = () => Flash('Error loading dynamic module'); - path = page.split(":"); + path = page.split(':'); shortcut_handler = null; - $(".js-gfm-input:not(.js-vue-textarea)").each((i, el) => { - const gfm = new GfmAutoComplete( - gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources - ); - const enableGFM = convertPermissionToBoolean( - el.dataset.supportsAutocomplete - ); + $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { + const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); + const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); gfm.setup($(el), { emojis: true, members: enableGFM, issues: enableGFM, milestones: enableGFM, mergeRequests: enableGFM, - labels: enableGFM + labels: enableGFM, }); }); @@ -137,335 +132,301 @@ import Activities from "./activities"; new LineHighlighter(); new BlobLinePermalinkUpdater( - document.querySelector("#blob-content-holder"), - ".diff-line-num[data-line-number]", - document.querySelectorAll( - ".js-data-file-blob-permalink-url, .js-blob-blame-link" - ) + document.querySelector('#blob-content-holder'), + '.diff-line-num[data-line-number]', + document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'), ); shortcut_handler = new ShortcutsNavigation(); - fileBlobPermalinkUrlElement = document.querySelector( - ".js-data-file-blob-permalink-url" - ); - fileBlobPermalinkUrl = - fileBlobPermalinkUrlElement && - fileBlobPermalinkUrlElement.getAttribute("href"); + fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); + fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); new ShortcutsBlob({ skipResetBindings: true, - fileBlobPermalinkUrl + fileBlobPermalinkUrl, }); new BlobForkSuggestion({ - openButtons: document.querySelectorAll( - ".js-edit-blob-link-fork-toggler" - ), - forkButtons: document.querySelectorAll(".js-fork-suggestion-button"), - cancelButtons: document.querySelectorAll( - ".js-cancel-fork-suggestion-button" - ), - suggestionSections: document.querySelectorAll( - ".js-file-fork-suggestion-section" - ), - actionTextPieces: document.querySelectorAll( - ".js-file-fork-suggestion-section-action" - ) - }).init(); + openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'), + forkButtons: document.querySelectorAll('.js-fork-suggestion-button'), + cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'), + suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'), + actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'), + }) + .init(); } - const filteredSearchEnabled = - gl.FilteredSearchManager && document.querySelector(".filtered-search"); + const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); switch (page) { - case "profiles:preferences:show": + case 'profiles:preferences:show': initExperimentalFlags(); break; - case "sessions:new": + case 'sessions:new': new UsernameValidator(); new SigninTabsMemoizer(); - new OAuthRememberMe({ - container: $(".omniauth-container") - }).bindEvents(); + new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents(); break; - case "projects:boards:show": - case "projects:boards:index": + case 'projects:boards:show': + case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); new UsersSelect(); break; - case "projects:merge_requests:index": - case "projects:issues:index": + case 'projects:merge_requests:index': + case 'projects:issues:index': if (filteredSearchEnabled) { - const filteredSearchManager = new gl.FilteredSearchManager( - page === "projects:issues:index" ? "issues" : "merge_requests" - ); + const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } - const pagePrefix = - page === "projects:merge_requests:index" - ? "merge_request_" - : "issue_"; + const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; new IssuableIndex(pagePrefix); shortcut_handler = new ShortcutsNavigation(); new UsersSelect(); break; - case "projects:issues:show": + case 'projects:issues:show': new Issue(); shortcut_handler = new ShortcutsIssuable(); new ZenMode(); initIssuableSidebar(); break; - case "dashboard:milestones:index": + case 'dashboard:milestones:index': projectSelect(); break; - case "projects:milestones:show": - case "groups:milestones:show": - case "dashboard:milestones:show": + case 'projects:milestones:show': + case 'groups:milestones:show': + case 'dashboard:milestones:show': new Milestone(); new Sidebar(); break; - case "dashboard:issues": - case "dashboard:merge_requests": + case 'dashboard:issues': + case 'dashboard:merge_requests': projectSelect(); initLegacyFilters(); break; - case "groups:issues": - case "groups:merge_requests": + case 'groups:issues': + case 'groups:merge_requests': if (filteredSearchEnabled) { - const filteredSearchManager = new gl.FilteredSearchManager( - page === "groups:issues" ? "issues" : "merge_requests" - ); + const filteredSearchManager = new gl.FilteredSearchManager(page === 'groups:issues' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } projectSelect(); break; - case "dashboard:todos:index": + case 'dashboard:todos:index': new Todos(); break; - case "explore:projects:index": - case "explore:projects:trending": - case "explore:projects:starred": - import("./pages/explore/projects") + + case 'explore:projects:index': + case 'explore:projects:trending': + case 'explore:projects:starred': + import('./pages/explore/projects') .then(module => module.default()) .catch(fail); break; - case "dashboard:projects:index": - case "dashboard:projects:starred": - case "admin:projects:index": + case 'dashboard:projects:index': + case 'dashboard:projects:starred': + case 'admin:projects:index': new ProjectsList(); break; - case "explore:groups:index": - import("./pages/explore/groups") + case 'explore:groups:index': + import('./pages/explore/groups') .then(module => module.default()) .catch(fail); break; - case "projects:milestones:new": - case "projects:milestones:edit": - case "projects:milestones:update": + + case 'admin:projects:index': + new ProjectsList(); + break; + case 'explore:groups:index': + new GroupsList(); + const landingElement = document.querySelector('.js-explore-groups-landing'); + if (!landingElement) break; + const exploreGroupsLanding = new Landing( + landingElement, + landingElement.querySelector('.dismiss-button'), + 'explore_groups_landing_dismissed', + ); + exploreGroupsLanding.toggle(); + break; + case 'projects:milestones:new': + case 'projects:milestones:edit': + case 'projects:milestones:update': new ZenMode(); new DueDateSelectors(); - new GLForm($(".milestone-form"), true); + new GLForm($('.milestone-form'), true); break; - case "groups:milestones:new": - case "groups:milestones:edit": - case "groups:milestones:update": + case 'groups:milestones:new': + case 'groups:milestones:edit': + case 'groups:milestones:update': new ZenMode(); new DueDateSelectors(); - new GLForm($(".milestone-form"), false); + new GLForm($('.milestone-form'), false); break; - case "projects:compare:show": + case 'projects:compare:show': new Diff(); const paddingTop = 16; - initChangesDropdown( - document.querySelector(".navbar-gitlab").offsetHeight - paddingTop - ); + initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop); break; - case "projects:branches:new": - case "projects:branches:create": - new NewBranchForm( - $(".js-create-branch-form"), - JSON.parse(document.getElementById("availableRefs").innerHTML) - ); + case 'projects:branches:new': + case 'projects:branches:create': + new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)); break; - case "projects:branches:index": + case 'projects:branches:index': AjaxLoadingSpinner.init(); new DeleteModal(); break; - case "projects:issues:new": - case "projects:issues:edit": + case 'projects:issues:new': + case 'projects:issues:edit': shortcut_handler = new ShortcutsNavigation(); - new GLForm($(".issue-form"), true); - new IssuableForm($(".issue-form")); + new GLForm($('.issue-form'), true); + new IssuableForm($('.issue-form')); new LabelsSelect(); new MilestoneSelect(); new IssuableTemplateSelectors(); break; - case "projects:merge_requests:creations:new": - const mrNewCompareNode = document.querySelector( - ".js-merge-request-new-compare" - ); + case 'projects:merge_requests:creations:new': + const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); if (mrNewCompareNode) { new Compare({ targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl, sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl, - targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl + targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl, }); } else { - const mrNewSubmitNode = document.querySelector( - ".js-merge-request-new-submit" - ); + const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit'); new MergeRequest({ - action: mrNewSubmitNode.dataset.mrSubmitAction + action: mrNewSubmitNode.dataset.mrSubmitAction, }); } - case "projects:merge_requests:creations:diffs": - case "projects:merge_requests:edit": + case 'projects:merge_requests:creations:diffs': + case 'projects:merge_requests:edit': new Diff(); shortcut_handler = new ShortcutsNavigation(); - new GLForm($(".merge-request-form"), true); - new IssuableForm($(".merge-request-form")); + new GLForm($('.merge-request-form'), true); + new IssuableForm($('.merge-request-form')); new LabelsSelect(); new MilestoneSelect(); new IssuableTemplateSelectors(); - new AutoWidthDropdownSelect($(".js-target-branch-select")).init(); + new AutoWidthDropdownSelect($('.js-target-branch-select')).init(); break; - case "projects:tags:new": + case 'projects:tags:new': new ZenMode(); - new GLForm($(".tag-form"), true); - new RefSelectDropdown($(".js-branch-select")); + new GLForm($('.tag-form'), true); + new RefSelectDropdown($('.js-branch-select')); break; - case "projects:snippets:show": + case 'projects:snippets:show': initNotes(); new ZenMode(); break; - case "projects:snippets:new": - case "projects:snippets:edit": - case "projects:snippets:create": - case "projects:snippets:update": - new GLForm($(".snippet-form"), true); + case 'projects:snippets:new': + case 'projects:snippets:edit': + case 'projects:snippets:create': + case 'projects:snippets:update': + new GLForm($('.snippet-form'), true); new ZenMode(); break; - case "snippets:new": - case "snippets:edit": - case "snippets:create": - case "snippets:update": - new GLForm($(".snippet-form"), false); + case 'snippets:new': + case 'snippets:edit': + case 'snippets:create': + case 'snippets:update': + new GLForm($('.snippet-form'), false); new ZenMode(); break; - case "projects:releases:edit": + case 'projects:releases:edit': new ZenMode(); - new GLForm($(".release-form"), true); + new GLForm($('.release-form'), true); break; - case "projects:merge_requests:show": + case 'projects:merge_requests:show': new Diff(); new ZenMode(); initIssuableSidebar(); initNotes(); - const mrShowNode = document.querySelector(".merge-request"); + const mrShowNode = document.querySelector('.merge-request'); window.mergeRequest = new MergeRequest({ - action: mrShowNode.dataset.mrAction + action: mrShowNode.dataset.mrAction, }); shortcut_handler = new ShortcutsIssuable(true); break; - case "dashboard:activity": + case 'dashboard:activity': new Activities(); break; - case "projects:commit:show": + case 'projects:commit:show': new Diff(); new ZenMode(); shortcut_handler = new ShortcutsNavigation(); new MiniPipelineGraph({ - container: ".js-commit-pipeline-graph" + container: '.js-commit-pipeline-graph', }).bindEvents(); initNotes(); const stickyBarPaddingTop = 16; - initChangesDropdown( - document.querySelector(".navbar-gitlab").offsetHeight - - stickyBarPaddingTop - ); - $(".commit-info.branches").load( - document.querySelector(".js-commit-box").dataset.commitPath - ); + initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); break; - case "projects:commit:pipelines": + case 'projects:commit:pipelines': new MiniPipelineGraph({ - container: ".js-commit-pipeline-graph" + container: '.js-commit-pipeline-graph', }).bindEvents(); - $(".commit-info.branches").load( - document.querySelector(".js-commit-box").dataset.commitPath - ); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); break; - case "projects:activity": + case 'projects:activity': new Activities(); shortcut_handler = new ShortcutsNavigation(); break; - case "projects:commits:show": - CommitsList.init( - document.querySelector(".js-project-commits-show").dataset - .commitsLimit - ); + case 'projects:commits:show': + CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); shortcut_handler = new ShortcutsNavigation(); GpgBadges.fetch(); break; - case "projects:show": + case 'projects:show': shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); new UserCallout({ setCalloutPerProject: true, - className: "js-autodevops-banner" + className: 'js-autodevops-banner', }); - if ($("#tree-slider").length) new TreeView(); - if ($(".blob-viewer").length) new BlobViewer(); - if ($(".project-show-activity").length) new Activities(); - $("#tree-slider").waitForImages(function() { - ajaxGet( - document.querySelector(".js-tree-content").dataset.logsPath - ); + if ($('#tree-slider').length) new TreeView(); + if ($('.blob-viewer').length) new BlobViewer(); + if ($('.project-show-activity').length) new Activities(); + $('#tree-slider').waitForImages(function() { + ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); break; - case "projects:edit": + case 'projects:edit': setupProjectEdit(); // Initialize expandable settings panels initSettingsPanels(); break; - case "projects:imports:show": + case 'projects:imports:show': projectImport(); break; - case "projects:pipelines:new": - case "projects:pipelines:create": - new NewBranchForm($(".js-new-pipeline-form")); - break; - case "projects:pipelines:builds": - case "projects:pipelines:failures": - case "projects:pipelines:show": - const { controllerAction } = document.querySelector( - ".js-pipeline-container" - ).dataset; - const pipelineStatusUrl = `${document - .querySelector(".js-pipeline-tab-link a") - .getAttribute("href")}/status.json`; + case 'projects:pipelines:new': + case 'projects:pipelines:create': + new NewBranchForm($('.js-new-pipeline-form')); + break; + case 'projects:pipelines:builds': + case 'projects:pipelines:failures': + case 'projects:pipelines:show': + const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; + const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`; new Pipelines({ initTabs: true, pipelineStatusUrl, tabsOptions: { action: controllerAction, - defaultAction: "pipelines", - parentEl: ".pipelines-tabs" - } + defaultAction: 'pipelines', + parentEl: '.pipelines-tabs', + }, }); break; - case "groups:activity": + case 'groups:activity': new Activities(); break; - case "groups:show": - const newGroupChildWrapper = document.querySelector( - ".js-new-project-subgroup" - ); + case 'groups:show': + const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup'); shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); notificationsDropdown(); @@ -475,273 +436,259 @@ import Activities from "./activities"; new NewGroupChild(newGroupChildWrapper); } break; - case "groups:group_members:index": + case 'groups:group_members:index': memberExpirationDate(); new Members(); new UsersSelect(); break; - case "projects:project_members:index": - memberExpirationDate(".js-access-expiration-date-groups"); + case 'projects:project_members:index': + memberExpirationDate('.js-access-expiration-date-groups'); groupsSelect(); memberExpirationDate(); new Members(); new UsersSelect(); break; - case "groups:new": - case "admin:groups:new": - case "groups:create": - case "admin:groups:create": + case 'groups:new': + case 'admin:groups:new': + case 'groups:create': + case 'admin:groups:create': BindInOut.initAll(); new Group(); groupAvatar(); break; - case "groups:edit": - case "admin:groups:edit": + case 'groups:edit': + case 'admin:groups:edit': groupAvatar(); break; - case "projects:tree:show": + case 'projects:tree:show': shortcut_handler = new ShortcutsNavigation(); new TreeView(); new BlobViewer(); - new NewCommitForm($(".js-create-dir-form")); - $("#tree-slider").waitForImages(function() { - ajaxGet( - document.querySelector(".js-tree-content").dataset.logsPath - ); + new NewCommitForm($('.js-create-dir-form')); + $('#tree-slider').waitForImages(function() { + ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); break; - case "projects:find_file:show": - const findElement = document.querySelector(".js-file-finder"); - const projectFindFile = new ProjectFindFile( - $(".file-finder-holder"), - { - url: findElement.dataset.fileFindUrl, - treeUrl: findElement.dataset.findTreeUrl, - blobUrlTemplate: findElement.dataset.blobUrlTemplate - } - ); + case 'projects:find_file:show': + const findElement = document.querySelector('.js-file-finder'); + const projectFindFile = new ProjectFindFile($(".file-finder-holder"), { + url: findElement.dataset.fileFindUrl, + treeUrl: findElement.dataset.findTreeUrl, + blobUrlTemplate: findElement.dataset.blobUrlTemplate, + }); new ShortcutsFindFile(projectFindFile); shortcut_handler = true; break; - case "projects:blob:show": + case 'projects:blob:show': new BlobViewer(); initBlob(); break; - case "projects:blame:show": + case 'projects:blame:show': initBlob(); break; - case "groups:labels:new": - case "groups:labels:edit": - case "projects:labels:new": - case "projects:labels:edit": + case 'groups:labels:new': + case 'groups:labels:edit': + case 'projects:labels:new': + case 'projects:labels:edit': new Labels(); break; - case "groups:labels:index": - case "projects:labels:index": - if ($(".prioritized-labels").length) { + case 'groups:labels:index': + case 'projects:labels:index': + if ($('.prioritized-labels').length) { new LabelManager(); } - $(".label-subscription").each((i, el) => { + $('.label-subscription').each((i, el) => { const $el = $(el); - if ($el.find(".dropdown-group-label").length) { + if ($el.find('.dropdown-group-label').length) { new GroupLabelSubscription($el); } else { new ProjectLabelSubscription($el); } }); break; - case "projects:network:show": + case 'projects:network:show': // Ensure we don't create a particular shortcut handler here. This is // already created, where the network graph is created. shortcut_handler = true; break; - case "projects:forks:new": - import(/* webpackChunkName: 'project_fork' */ "./project_fork") + case 'projects:forks:new': + import(/* webpackChunkName: 'project_fork' */ './project_fork') .then(fork => fork.default()) .catch(() => {}); break; - case "projects:artifacts:browse": + case 'projects:artifacts:browse': new ShortcutsNavigation(); new BuildArtifacts(); break; - case "projects:artifacts:file": + case 'projects:artifacts:file': new ShortcutsNavigation(); new BlobViewer(); break; - case "help:index": - VersionCheckImage.bindErrorEvent($("img.js-version-status-badge")); + case 'help:index': + VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); break; - case "search:show": + case 'search:show': new Search(); break; - case "projects:settings:repository:show": + case 'projects:settings:repository:show': // Initialize expandable settings panels initSettingsPanels(); break; - case "projects:settings:ci_cd:show": + case 'projects:settings:ci_cd:show': // Initialize expandable settings panels initSettingsPanels(); - const runnerToken = document.querySelector(".js-secret-runner-token"); + const runnerToken = document.querySelector('.js-secret-runner-token'); if (runnerToken) { const runnerTokenSecretValue = new SecretValues(runnerToken); runnerTokenSecretValue.init(); } - case "groups:settings:ci_cd:show": - const secretVariableTable = document.querySelector( - ".js-secret-variable-table" - ); + case 'groups:settings:ci_cd:show': + const secretVariableTable = document.querySelector('.js-secret-variable-table'); if (secretVariableTable) { - const secretVariableTableValues = new SecretValues( - secretVariableTable - ); + const secretVariableTableValues = new SecretValues(secretVariableTable); secretVariableTableValues.init(); } break; - case "ci:lints:create": - case "ci:lints:show": + case 'ci:lints:create': + case 'ci:lints:show': new CILintEditor(); break; - case "users:show": - import("./pages/users/show") - .then(m => m.default()) - .catch(fail); + case 'users:show': + import('./pages/users/show').then(m => m.default()).catch(fail); break; - case "admin:conversational_development_index:show": + case 'admin:conversational_development_index:show': new UserCallout(); break; - case "snippets:show": + case 'snippets:show': new LineHighlighter(); new BlobViewer(); initNotes(); new ZenMode(); break; - case "import:fogbugz:new_user_map": + case 'import:fogbugz:new_user_map': new UsersSelect(); break; - case "profiles:personal_access_tokens:index": - case "admin:impersonation_tokens:index": + case 'profiles:personal_access_tokens:index': + case 'admin:impersonation_tokens:index': new DueDateSelectors(); break; - case "projects:clusters:show": - import(/* webpackChunkName: "clusters" */ "./clusters/clusters_bundle") + case 'projects:clusters:show': + import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle') .then(cluster => new cluster.default()) // eslint-disable-line new-cap - .catch(err => { - Flash(s__("ClusterIntegration|Problem setting up the cluster")); + .catch((err) => { + Flash(s__('ClusterIntegration|Problem setting up the cluster')); throw err; }); break; - case "projects:clusters:index": - import(/* webpackChunkName: "clusters_index" */ "./clusters/clusters_index") + case 'projects:clusters:index': + import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index') .then(clusterIndex => clusterIndex.default()) - .catch(err => { - Flash( - s__("ClusterIntegration|Problem setting up the clusters list") - ); + .catch((err) => { + Flash(s__('ClusterIntegration|Problem setting up the clusters list')); throw err; }); break; } switch (path[0]) { - case "sessions": - case "omniauth_callbacks": + case 'sessions': + case 'omniauth_callbacks': if (!gon.u2f) break; const u2fAuthenticate = new U2FAuthenticate( - $("#js-authenticate-u2f"), - "#js-login-u2f-form", + $('#js-authenticate-u2f'), + '#js-login-u2f-form', gon.u2f, - document.querySelector("#js-login-2fa-device"), - document.querySelector(".js-2fa-form") + document.querySelector('#js-login-2fa-device'), + document.querySelector('.js-2fa-form'), ); u2fAuthenticate.start(); // needed in rspec gl.u2fAuthenticate = u2fAuthenticate; - case "admin": + case 'admin': initAdmin(); switch (path[1]) { - case "broadcast_messages": + case 'broadcast_messages': initBroadcastMessagesForm(); break; - case "cohorts": + case 'cohorts': new UsagePing(); break; - case "groups": + case 'groups': new UsersSelect(); break; - case "projects": - document - .querySelectorAll(".js-namespace-select") + case 'projects': + document.querySelectorAll('.js-namespace-select') .forEach(dropdown => new NamespaceSelect({ dropdown })); break; - case "labels": + case 'labels': switch (path[2]) { - case "new": - case "edit": + case 'new': + case 'edit': new Labels(); } - case "abuse_reports": + case 'abuse_reports': new AbuseReports(); break; } break; - case "dashboard": - case "root": + case 'dashboard': + case 'root': new UserCallout(); break; - case "profiles": + case 'profiles': new NotificationsForm(); notificationsDropdown(); break; - case "projects": + case 'projects': new Project(); projectAvatar(); switch (path[1]) { - case "compare": + case 'compare': initCompareAutocomplete(); break; - case "edit": + case 'edit': shortcut_handler = new ShortcutsNavigation(); new ProjectNew(); - import(/* webpackChunkName: 'project_permissions' */ "./projects/permissions") + import(/* webpackChunkName: 'project_permissions' */ './projects/permissions') .then(permissions => permissions.default()) .catch(() => {}); break; - case "new": + case 'new': new ProjectNew(); initProjectVisibilitySelector(); break; - case "show": + case 'show': new Star(); new ProjectNew(); notificationsDropdown(); break; - case "wikis": + case 'wikis': new Wikis(); shortcut_handler = new ShortcutsWiki(); new ZenMode(); - new GLForm($(".wiki-form"), true); + new GLForm($('.wiki-form'), true); break; - case "snippets": + case 'snippets': shortcut_handler = new ShortcutsNavigation(); - if (path[2] === "show") { + if (path[2] === 'show') { new ZenMode(); new LineHighlighter(); new BlobViewer(); } break; - case "labels": - case "graphs": - case "compare": - case "pipelines": - case "forks": - case "milestones": - case "project_members": - case "deploy_keys": - case "builds": - case "hooks": - case "services": - case "protected_branches": + case 'labels': + case 'graphs': + case 'compare': + case 'pipelines': + case 'forks': + case 'milestones': + case 'project_members': + case 'deploy_keys': + case 'builds': + case 'hooks': + case 'services': + case 'protected_branches': shortcut_handler = new ShortcutsNavigation(); } break; @@ -751,20 +698,20 @@ import Activities from "./activities"; new Shortcuts(); } - if (document.querySelector("#peek")) { - new PerformanceBar({ container: "#peek" }); + if (document.querySelector('#peek')) { + new PerformanceBar({ container: '#peek' }); } }; Dispatcher.prototype.initSearch = function() { // Only when search form is present - if ($(".search").length) { + if ($('.search').length) { return new SearchAutocomplete(); } }; Dispatcher.prototype.initFieldErrors = function() { - $(".gl-show-field-errors").each((i, form) => { + $('.gl-show-field-errors').each((i, form) => { new GlFieldErrors(form); }); }; @@ -772,7 +719,7 @@ import Activities from "./activities"; return Dispatcher; })(); - $(window).on("load", function() { + $(window).on('load', function() { new Dispatcher(); }); -}.call(window)); +}).call(window); diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js index 97c4e2dc869..6515202a31b 100644 --- a/app/assets/javascripts/pages/explore/groups/index.js +++ b/app/assets/javascripts/pages/explore/groups/index.js @@ -1,14 +1,14 @@ -import GroupsList from "~/groups_list"; -import Landing from "~/landing"; +import GroupsList from '~/groups_list'; +import Landing from '~/landing'; export default function() { new GroupsList(); - const landingElement = document.querySelector(".js-explore-groups-landing"); + const landingElement = document.querySelector('.js-explore-groups-landing'); if (!landingElement) return; const exploreGroupsLanding = new Landing( landingElement, - landingElement.querySelector(".dismiss-button"), - "explore_groups_landing_dismissed" + landingElement.querySelector('.dismiss-button'), + 'explore_groups_landing_dismissed' ); exploreGroupsLanding.toggle(); } diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js index 0bbb82b9daf..2259cb7a5cf 100644 --- a/app/assets/javascripts/pages/explore/projects/index.js +++ b/app/assets/javascripts/pages/explore/projects/index.js @@ -1,4 +1,4 @@ -import ProjectsList from "~/projects_list"; +import ProjectsList from '~/projects_list'; export default function() { new ProjectsList(); -- cgit v1.2.1 From cc67e91415f562fd19cc9768c7e451bc937c8264 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 8 Jan 2018 16:40:19 -0500 Subject: Fixes duplication --- app/assets/javascripts/dispatcher.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index af516a8e7ff..96802f167ba 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -214,7 +214,6 @@ import Activities from './activities'; case 'dashboard:todos:index': new Todos(); break; - case 'explore:projects:index': case 'explore:projects:trending': case 'explore:projects:starred': @@ -232,21 +231,6 @@ import Activities from './activities'; .then(module => module.default()) .catch(fail); break; - - case 'admin:projects:index': - new ProjectsList(); - break; - case 'explore:groups:index': - new GroupsList(); - const landingElement = document.querySelector('.js-explore-groups-landing'); - if (!landingElement) break; - const exploreGroupsLanding = new Landing( - landingElement, - landingElement.querySelector('.dismiss-button'), - 'explore_groups_landing_dismissed', - ); - exploreGroupsLanding.toggle(); - break; case 'projects:milestones:new': case 'projects:milestones:edit': case 'projects:milestones:update': -- cgit v1.2.1 From a75bbbd92ad28f8d731231104e65d6aeeca1256c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 8 Jan 2018 19:40:23 -0200 Subject: Make Rubocop happy --- spec/tasks/gitlab/uploads_rake_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb index 9330539f8c9..0c779d8a135 100644 --- a/spec/tasks/gitlab/uploads_rake_spec.rb +++ b/spec/tasks/gitlab/uploads_rake_spec.rb @@ -13,7 +13,7 @@ describe 'gitlab:uploads rake tasks' do end it 'errors out about missing files on the file system' do - uploaded_file = create(:upload) + create(:upload) expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout end -- cgit v1.2.1 From 9afb77b6d409ce385b872de430c561b0ee9ae29a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 8 Jan 2018 19:43:53 -0200 Subject: Refactoring spec for the gitlab:uploads:check rake task --- spec/tasks/gitlab/uploads_rake_spec.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb index 0c779d8a135..ac0005e51e0 100644 --- a/spec/tasks/gitlab/uploads_rake_spec.rb +++ b/spec/tasks/gitlab/uploads_rake_spec.rb @@ -2,14 +2,14 @@ require 'rake_helper' describe 'gitlab:uploads rake tasks' do describe 'check' do + let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) } + before do Rake.application.rake_require 'tasks/gitlab/uploads' end it 'outputs the integrity check for each uploaded file' do - uploaded_file = create(:upload) - - expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{uploaded_file.id}\): #{Regexp.quote(uploaded_file.absolute_path)}/).to_stdout + expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{upload.id}\): #{Regexp.quote(upload.absolute_path)}/).to_stdout end it 'errors out about missing files on the file system' do @@ -19,10 +19,9 @@ describe 'gitlab:uploads rake tasks' do end it 'errors out about invalid checksum' do - uploaded_file = create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) - uploaded_file.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e') + upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e') - expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{uploaded_file.checksum}\)/).to_stdout + expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{upload.checksum}\)/).to_stdout end end end -- cgit v1.2.1 From a9a858e0200cab8009fe17acee58dcb7aecb6c8e Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 8 Jan 2018 17:40:12 -0500 Subject: Fix `projects:a*` refactor for dispatcher --- app/assets/javascripts/dispatcher.js | 6 ++++-- app/assets/javascripts/pages/projects/activity/index.js | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/activity/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 9e8b2acfe1b..edfc9088d9a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -357,8 +357,10 @@ import Activities from './activities'; $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); break; case 'projects:activity': - new Activities(); - shortcut_handler = new ShortcutsNavigation(); + import('./pages/projects/activity') + .then(callDefault) + .catch(fail); + shortcut_handler = true; break; case 'projects:commits:show': CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); diff --git a/app/assets/javascripts/pages/projects/activity/index.js b/app/assets/javascripts/pages/projects/activity/index.js new file mode 100644 index 00000000000..7af95127fd5 --- /dev/null +++ b/app/assets/javascripts/pages/projects/activity/index.js @@ -0,0 +1,7 @@ +import Activities from '~/activities'; +import ShortcutsNavigation from '~/shortcuts_navigation'; + +export default function () { + new Activities(); // eslint-disable-line no-new + new ShortcutsNavigation(); // eslint-disable-line no-new +} -- cgit v1.2.1 From 2bd4453ca2c4bec527a264194cf12d164cd31ed7 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 8 Jan 2018 18:08:32 -0500 Subject: Fix eslint --- app/assets/javascripts/dispatcher.js | 14 ++++++-------- app/assets/javascripts/pages/explore/groups/index.js | 6 +++--- app/assets/javascripts/pages/explore/projects/index.js | 4 ++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f7421ea415d..11ec667fc07 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -38,12 +38,10 @@ import BindInOut from './behaviors/bind_in_out'; import SecretValues from './behaviors/secret_values'; import DeleteModal from './branches/branches_delete_modal'; import Group from './group'; -import GroupsList from './groups_list'; import ProjectsList from './projects_list'; import setupProjectEdit from './project_edit'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; -import Landing from './landing'; import BlobForkSuggestion from './blob/blob_fork_suggestion'; import UserCallout from './user_callout'; import ShortcutsWiki from './shortcuts_wiki'; @@ -218,7 +216,7 @@ import Activities from './activities'; case 'explore:projects:trending': case 'explore:projects:starred': import('./pages/explore/projects') - .then(module => module.default()) + .then(callDefault) .catch(fail); break; case 'dashboard:projects:index': @@ -228,7 +226,7 @@ import Activities from './activities'; break; case 'explore:groups:index': import('./pages/explore/groups') - .then(module => module.default()) + .then(callDefault) .catch(fail); break; case 'projects:milestones:new': @@ -498,8 +496,8 @@ import Activities from './activities'; break; case 'projects:forks:new': import(/* webpackChunkName: 'project_fork' */ './project_fork') - .then(fork => fork.default()) - .catch(() => {}); + .then(callDefault) + .catch(fail); break; case 'projects:artifacts:browse': new ShortcutsNavigation(); @@ -635,8 +633,8 @@ import Activities from './activities'; shortcut_handler = new ShortcutsNavigation(); new ProjectNew(); import(/* webpackChunkName: 'project_permissions' */ './projects/permissions') - .then(permissions => permissions.default()) - .catch(() => {}); + .then(callDefault) + .catch(fail); break; case 'new': new ProjectNew(); diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js index 6515202a31b..859b073f1cb 100644 --- a/app/assets/javascripts/pages/explore/groups/index.js +++ b/app/assets/javascripts/pages/explore/groups/index.js @@ -1,14 +1,14 @@ import GroupsList from '~/groups_list'; import Landing from '~/landing'; -export default function() { - new GroupsList(); +export default function () { + new GroupsList(); // eslint-disable-line no-new const landingElement = document.querySelector('.js-explore-groups-landing'); if (!landingElement) return; const exploreGroupsLanding = new Landing( landingElement, landingElement.querySelector('.dismiss-button'), - 'explore_groups_landing_dismissed' + 'explore_groups_landing_dismissed', ); exploreGroupsLanding.toggle(); } diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js index 2259cb7a5cf..93acb54d4f1 100644 --- a/app/assets/javascripts/pages/explore/projects/index.js +++ b/app/assets/javascripts/pages/explore/projects/index.js @@ -1,5 +1,5 @@ import ProjectsList from '~/projects_list'; -export default function() { - new ProjectsList(); +export default function () { + new ProjectsList(); // eslint-disable-line no-new } -- cgit v1.2.1 From 3ef346bce93c624ef069949286127a9403b49ad6 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 8 Jan 2018 20:31:27 -0500 Subject: Finishes dispatcher `projects:a*` stuff. --- app/assets/javascripts/dispatcher.js | 13 ++++++++----- .../javascripts/pages/projects/artifacts/browse/index.js | 7 +++++++ .../javascripts/pages/projects/artifacts/file/index.js | 7 +++++++ 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/artifacts/browse/index.js create mode 100644 app/assets/javascripts/pages/projects/artifacts/file/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index edfc9088d9a..fe59bcbd477 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -12,7 +12,6 @@ import notificationsDropdown from './notifications_dropdown'; import groupAvatar from './group_avatar'; import GroupLabelSubscription from './group_label_subscription'; import LineHighlighter from './line_highlighter'; -import BuildArtifacts from './build_artifacts'; import CILintEditor from './ci_lint_editor'; import groupsSelect from './groups_select'; import Search from './search'; @@ -506,12 +505,16 @@ import Activities from './activities'; .catch(() => {}); break; case 'projects:artifacts:browse': - new ShortcutsNavigation(); - new BuildArtifacts(); + import('./pages/projects/artifacts/browse') + .then(callDefault) + .catch(fail); + shortcut_handler = true; break; case 'projects:artifacts:file': - new ShortcutsNavigation(); - new BlobViewer(); + import('./pages/projects/artifacts/file') + .then(callDefault) + .catch(fail); + shortcut_handler = true; break; case 'help:index': VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); diff --git a/app/assets/javascripts/pages/projects/artifacts/browse/index.js b/app/assets/javascripts/pages/projects/artifacts/browse/index.js new file mode 100644 index 00000000000..02456071086 --- /dev/null +++ b/app/assets/javascripts/pages/projects/artifacts/browse/index.js @@ -0,0 +1,7 @@ +import BuildArtifacts from '~/build_artifacts'; +import ShortcutsNavigation from '~/shortcuts_navigation'; + +export default function () { + new ShortcutsNavigation(); // eslint-disable-line no-new + new BuildArtifacts(); // eslint-disable-line no-new +} diff --git a/app/assets/javascripts/pages/projects/artifacts/file/index.js b/app/assets/javascripts/pages/projects/artifacts/file/index.js new file mode 100644 index 00000000000..4cd67ac76e3 --- /dev/null +++ b/app/assets/javascripts/pages/projects/artifacts/file/index.js @@ -0,0 +1,7 @@ +import BlobViewer from '~/blob/viewer/index'; +import ShortcutsNavigation from '~/shortcuts_navigation'; + +export default function () { + new ShortcutsNavigation(); // eslint-disable-line no-new + new BlobViewer(); // eslint-disable-line no-new +} -- cgit v1.2.1 From 89fd16262d1fd3d986003a29a9171d3b84f2c522 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 8 Jan 2018 14:26:42 +0000 Subject: Added import for snippets:show in dispatcher #41341 --- app/assets/javascripts/dispatcher.js | 5 +---- app/assets/javascripts/pages/snippets/show/index.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/pages/snippets/show/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 9e8b2acfe1b..767705517a7 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -548,10 +548,7 @@ import Activities from './activities'; new UserCallout(); break; case 'snippets:show': - new LineHighlighter(); - new BlobViewer(); - initNotes(); - new ZenMode(); + import('./pages/snippets/show').then(m => m.default()).catch(fail); break; case 'import:fogbugz:new_user_map': new UsersSelect(); diff --git a/app/assets/javascripts/pages/snippets/show/index.js b/app/assets/javascripts/pages/snippets/show/index.js new file mode 100644 index 00000000000..04c9562bfbb --- /dev/null +++ b/app/assets/javascripts/pages/snippets/show/index.js @@ -0,0 +1,12 @@ +/* eslint-disable no-new */ +import LineHighlighter from '../../../line_highlighter'; +import BlobViewer from '../../../blob/viewer'; +import ZenMode from '../../../zen_mode'; +import initNotes from '../../../init_notes'; + +export default () => { + new LineHighlighter(); + new BlobViewer(); + initNotes(); + new ZenMode(); +}; -- cgit v1.2.1 From 506717d0cae67f58bf6e590b7122fd6e8b13a406 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 8 Jan 2018 14:29:00 +0000 Subject: Added ci:lints import to dispatcher --- app/assets/javascripts/ci_lint_editor.js | 12 ------------ app/assets/javascripts/dispatcher.js | 3 +-- app/assets/javascripts/pages/ci/lints/ci_lint_editor.js | 12 ++++++++++++ app/assets/javascripts/pages/ci/lints/index.js | 3 +++ 4 files changed, 16 insertions(+), 14 deletions(-) delete mode 100644 app/assets/javascripts/ci_lint_editor.js create mode 100644 app/assets/javascripts/pages/ci/lints/ci_lint_editor.js create mode 100644 app/assets/javascripts/pages/ci/lints/index.js diff --git a/app/assets/javascripts/ci_lint_editor.js b/app/assets/javascripts/ci_lint_editor.js deleted file mode 100644 index b9469e5b7cb..00000000000 --- a/app/assets/javascripts/ci_lint_editor.js +++ /dev/null @@ -1,12 +0,0 @@ -export default class CILintEditor { - constructor() { - this.editor = window.ace.edit('ci-editor'); - this.textarea = document.querySelector('#content'); - - this.editor.getSession().setMode('ace/mode/yaml'); - this.editor.on('input', () => { - const content = this.editor.getSession().getValue(); - this.textarea.value = content; - }); - } -} diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 767705517a7..f9e37926509 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -13,7 +13,6 @@ import groupAvatar from './group_avatar'; import GroupLabelSubscription from './group_label_subscription'; import LineHighlighter from './line_highlighter'; import BuildArtifacts from './build_artifacts'; -import CILintEditor from './ci_lint_editor'; import groupsSelect from './groups_select'; import Search from './search'; import initAdmin from './admin'; @@ -539,7 +538,7 @@ import Activities from './activities'; break; case 'ci:lints:create': case 'ci:lints:show': - new CILintEditor(); + import('./pages/ci/lints').then(m => m.default()).catch(fail); break; case 'users:show': import('./pages/users/show').then(callDefault).catch(fail); diff --git a/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js new file mode 100644 index 00000000000..b9469e5b7cb --- /dev/null +++ b/app/assets/javascripts/pages/ci/lints/ci_lint_editor.js @@ -0,0 +1,12 @@ +export default class CILintEditor { + constructor() { + this.editor = window.ace.edit('ci-editor'); + this.textarea = document.querySelector('#content'); + + this.editor.getSession().setMode('ace/mode/yaml'); + this.editor.on('input', () => { + const content = this.editor.getSession().getValue(); + this.textarea.value = content; + }); + } +} diff --git a/app/assets/javascripts/pages/ci/lints/index.js b/app/assets/javascripts/pages/ci/lints/index.js new file mode 100644 index 00000000000..5cc66546109 --- /dev/null +++ b/app/assets/javascripts/pages/ci/lints/index.js @@ -0,0 +1,3 @@ +import CILintEditor from './ci_lint_editor'; + +export default () => new CILintEditor(); -- cgit v1.2.1 From 90d4d9b5f5c3a2d90a5c1c28dfe45b6887f299f5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 8 Jan 2018 14:31:39 +0000 Subject: Added import:fogbugz:new_user_map import to dispatcher --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f9e37926509..816fbb5b98e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -550,7 +550,7 @@ import Activities from './activities'; import('./pages/snippets/show').then(m => m.default()).catch(fail); break; case 'import:fogbugz:new_user_map': - new UsersSelect(); + import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail); break; case 'profiles:personal_access_tokens:index': case 'admin:impersonation_tokens:index': diff --git a/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js b/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js new file mode 100644 index 00000000000..5defea104d4 --- /dev/null +++ b/app/assets/javascripts/pages/import/fogbugz/new_user_map/index.js @@ -0,0 +1,3 @@ +import UsersSelect from '../../../../users_select'; + +export default () => new UsersSelect(); -- cgit v1.2.1 From 2f86f64f3481947c534ab5840e3cecdd6b317671 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 8 Jan 2018 14:34:39 +0000 Subject: Added admin:conversational_development_index:show import to dispatcher --- app/assets/javascripts/dispatcher.js | 2 +- .../pages/admin/conversational_development_index/show/index.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/pages/admin/conversational_development_index/show/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 816fbb5b98e..1f554be6046 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -544,7 +544,7 @@ import Activities from './activities'; import('./pages/users/show').then(callDefault).catch(fail); break; case 'admin:conversational_development_index:show': - new UserCallout(); + import('./pages/admin/conversational_development_index/show').then(m => m.default()).catch(fail); break; case 'snippets:show': import('./pages/snippets/show').then(m => m.default()).catch(fail); 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 new file mode 100644 index 00000000000..6e66ef69fe1 --- /dev/null +++ b/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js @@ -0,0 +1,3 @@ +import UserCallout from '../../../../user_callout'; + +export default () => new UserCallout(); -- cgit v1.2.1 From 4b945e2845cf40456ef71faafbce181f0e60dba7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 10:02:55 +0100 Subject: Make it possible to define a QA-specific page element --- qa/qa/page/element.rb | 19 ++++++++++--------- qa/qa/page/view.rb | 2 +- qa/spec/page/element_spec.rb | 21 ++++++++++++--------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 173fdbf6d36..af93a575cb6 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -3,21 +3,22 @@ module QA class Element attr_reader :name - def initialize(name, pattern) + def initialize(name, pattern = nil) @name = name - @pattern = pattern + @pattern = pattern || "qa-#{@name.to_s.gsub('_', '-')}" end - def matches?(line) - case @pattern - when Regexp - !!(line =~ @pattern) - when String - line.include?(@pattern) + def expression + if @pattern.is_a?(String) + @_regexp ||= Regexp.new(Regexp.escape(@pattern)) else - raise ArgumentError, 'Pattern should be either String or Regexp!' + @pattern end end + + def matches?(line) + !!(line =~ expression) + end end end end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index fa0ed8be9d9..47658144783 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -46,7 +46,7 @@ module QA @elements = [] end - def element(name, pattern) + def element(name, pattern = nil) @elements.push(Page::Element.new(name, pattern)) end end diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index 238c4d1ac66..c665267b90e 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -2,11 +2,11 @@ describe QA::Page::Element do context 'when pattern is an expression' do subject { described_class.new(:something, /button 'Sign in'/) } - it 'is correctly matches against a string' do + it 'matches when there is a match' do expect(subject.matches?("button 'Sign in'")).to be true end - it 'does not match if string does not match against a pattern' do + it 'does not match if pattern is not present' do expect(subject.matches?("button 'Sign out'")).to be false end end @@ -14,21 +14,24 @@ describe QA::Page::Element do context 'when pattern is a string' do subject { described_class.new(:something, 'button') } - it 'is correctly matches against a string' do + it 'matches when there is match' do expect(subject.matches?('some button in the view')).to be true end - it 'does not match if string does not match against a pattern' do + it 'does not match if pattern is not present' do expect(subject.matches?('text_field :name')).to be false end end - context 'when pattern is not supported' do - subject { described_class.new(:something, [/something/]) } + context 'when pattern is not provided' do + subject { described_class.new(:some_name) } - it 'raises an error' do - expect { subject.matches?('some line') } - .to raise_error ArgumentError + it 'matches when QA specific selector is present' do + expect(subject.matches?('some qa-some-name selector')).to be true + end + + it 'does not match if QA selector is not there' do + expect(subject.matches?('some_name selector')).to be false end end end -- cgit v1.2.1 From d5a92c53dd79ff661ca7f8e4b0deefec5e19704d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 11:28:14 +0100 Subject: Implement QA pages and views validator --- qa/qa.rb | 1 + qa/qa/page/validator.rb | 51 ++++++++++++++++++++++++++ qa/spec/page/validator_spec.rb | 83 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 qa/qa/page/validator.rb create mode 100644 qa/spec/page/validator_spec.rb diff --git a/qa/qa.rb b/qa/qa.rb index 9ee4e6b9d0c..4803432aeee 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -74,6 +74,7 @@ module QA autoload :Base, 'qa/page/base' autoload :View, 'qa/page/view' autoload :Element, 'qa/page/element' + autoload :Validator, 'qa/page/validator' module Main autoload :Login, 'qa/page/main/login' diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb new file mode 100644 index 00000000000..cf4e57db0f0 --- /dev/null +++ b/qa/qa/page/validator.rb @@ -0,0 +1,51 @@ +module QA + module Page + class Validator + ValidationError = Class.new(StandardError) + Error = Struct.new(:page, :view, :message) + + def initialize(constant) + @module = constant + end + + def constants + @consts ||= @module.constants.map do |const| + @module.const_get(const) + end + end + + def descendants + @descendants ||= constants.map do |const| + case const + when Class + const if const < Page::Base + when Module + Page::Validator.new(const).descendants + end + end + + @descendants.flatten.compact + end + + def errors + @errors ||= Array.new.tap do |errors| + descendants.each do |page| + page.views.each do |view| + view.errors.each do |error| + errors.push(Error.new(page, view, error)) + end + end + end + end + end + + def validate! + message = <<~EOS + We found validation errors! + EOS + + raise ValidationError, message if errors.any? + end + end + end +end diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb new file mode 100644 index 00000000000..abee137f4a1 --- /dev/null +++ b/qa/spec/page/validator_spec.rb @@ -0,0 +1,83 @@ +describe QA::Page::Validator do + describe '#constants' do + subject do + described_class.new(QA::Page::Project) + end + + it 'returns all costants that are module children' do + expect(subject.constants) + .to include QA::Page::Project::New, QA::Page::Project::Settings + end + end + + describe '#descendants' do + subject do + described_class.new(QA::Page::Project) + end + + it 'recursively returns all descendants that are page objects' do + expect(subject.descendants) + .to include QA::Page::Project::New, QA::Page::Project::Settings::Repository + end + + it 'does not return modules that aggregate page objects' do + expect(subject.descendants) + .not_to include QA::Page::Project::Settings + end + end + + context 'when checking validation errors' do + let(:view) { spy('view') } + + before do + allow(QA::Page::Admin::Settings) + .to receive(:views).and_return([view]) + end + + subject do + described_class.new(QA::Page::Admin) + end + + context 'when there are no validation errors' do + before do + allow(view).to receive(:errors).and_return([]) + end + + describe '#errors' do + it 'does not return errors' do + expect(subject.errors).to be_empty + end + end + + describe '#validate!' do + it 'does not raise error' do + expect { subject.validate! }.not_to raise_error + end + end + end + + context 'when there are validation errors' do + before do + allow(view).to receive(:errors) + .and_return(['some error', 'another error']) + end + + describe '#errors' do + it 'returns errors' do + expect(subject.errors.count).to eq 2 + end + end + + describe '#validate!' do + it 'does raises an error with descriptive message' do + message = <<~EOS + We found validation errors! + EOS + + expect { subject.validate! } + .to raise_error described_class::ValidationError, message + end + end + end + end +end -- cgit v1.2.1 From a5cfd5a69e61b915e8d5a15114441630ff8d37c7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 11:29:19 +0100 Subject: Fix rubocop offense in QA page element class --- qa/qa/page/element.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index af93a575cb6..23e1b10fe33 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -5,7 +5,7 @@ module QA def initialize(name, pattern = nil) @name = name - @pattern = pattern || "qa-#{@name.to_s.gsub('_', '-')}" + @pattern = pattern || "qa-#{@name.to_s.tr('_', '-')}" end def expression -- cgit v1.2.1 From fa07d232247e0ae393bb692676fcd6b3f1f0e5c2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 12:06:58 +0100 Subject: Add QA error when page class has no views defined --- qa/qa/page/base.rb | 4 ++++ qa/qa/page/validator.rb | 10 ++++------ qa/qa/scenario/test/sanity/selectors.rb | 11 +++++++++++ qa/spec/page/base_spec.rb | 25 +++++++++++++++++++------ qa/spec/page/validator_spec.rb | 8 ++------ 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 9064c78b792..ba1323eb215 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -49,6 +49,10 @@ module QA end def self.errors + if views.empty? + return ["#{name} class does not have views / elements defined!"] + end + @errors ||= views.map(&:errors).flatten end diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb index cf4e57db0f0..88d083f5d97 100644 --- a/qa/qa/page/validator.rb +++ b/qa/qa/page/validator.rb @@ -31,8 +31,8 @@ module QA @errors ||= Array.new.tap do |errors| descendants.each do |page| page.views.each do |view| - view.errors.each do |error| - errors.push(Error.new(page, view, error)) + view.errors.each do |message| + errors.push(Error.new(page.name, view.path, message)) end end end @@ -40,11 +40,9 @@ module QA end def validate! - message = <<~EOS - We found validation errors! - EOS + return if errors.none? - raise ValidationError, message if errors.any? + raise ValidationError, 'Page views / elements validation error!' end end end diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index 892bb2966c7..c6ede07680a 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -5,7 +5,18 @@ module QA class Selectors < Scenario::Template include Scenario::Bootable + PAGE_MODULES = [QA::Page] + def perform(*) + validators = PAGE_MODULES.map do |pages| + Page::Validator.new(pages) + end + + validators.map(&:errors).flatten.tap do |errors| + + end + + validators.each(&:validate!) end end end diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb index 63445d8f7bf..29b62cf758c 100644 --- a/qa/spec/page/base_spec.rb +++ b/qa/spec/page/base_spec.rb @@ -36,15 +36,28 @@ describe QA::Page::Base do describe '.errors' do let(:view) { double('view') } - before do - allow(described_class).to receive(:views) - .and_return([view]) + context 'when page has views and elements defined' do + before do + allow(described_class).to receive(:views) + .and_return([view]) - allow(view).to receive(:errors).and_return(['some error']) + allow(view).to receive(:errors).and_return(['some error']) + end + + it 'iterates views composite and returns errors' do + expect(described_class.errors).to eq ['some error'] + end end - it 'iterates views composite and returns errors' do - expect(described_class.errors).to eq ['some error'] + context 'when page has no views and elements defined' do + before do + allow(described_class).to receive(:views).and_return([]) + end + + it 'appends an error about missing views / elements block' do + expect(described_class.errors) + .to include 'QA::Page::Base class does not have views / elements defined!' + end end end end diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb index abee137f4a1..e13fb1eae5b 100644 --- a/qa/spec/page/validator_spec.rb +++ b/qa/spec/page/validator_spec.rb @@ -69,13 +69,9 @@ describe QA::Page::Validator do end describe '#validate!' do - it 'does raises an error with descriptive message' do - message = <<~EOS - We found validation errors! - EOS - + it 'raises validation error' do expect { subject.validate! } - .to raise_error described_class::ValidationError, message + .to raise_error described_class::ValidationError end end end -- cgit v1.2.1 From 170c07ef677d45edf93411f6c5a396b93e0def3f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 12:20:39 +0100 Subject: Fix QA pages validation delegation and memoization --- qa/qa/page/base.rb | 2 +- qa/qa/page/validator.rb | 10 ++++------ qa/qa/scenario/test/sanity/selectors.rb | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index ba1323eb215..05e300820fa 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -53,7 +53,7 @@ module QA return ["#{name} class does not have views / elements defined!"] end - @errors ||= views.map(&:errors).flatten + views.map(&:errors).flatten end class DSL diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb index 88d083f5d97..953a137ec34 100644 --- a/qa/qa/page/validator.rb +++ b/qa/qa/page/validator.rb @@ -2,7 +2,7 @@ module QA module Page class Validator ValidationError = Class.new(StandardError) - Error = Struct.new(:page, :view, :message) + Error = Struct.new(:page, :message) def initialize(constant) @module = constant @@ -28,12 +28,10 @@ module QA end def errors - @errors ||= Array.new.tap do |errors| + Array.new.tap do |errors| descendants.each do |page| - page.views.each do |view| - view.errors.each do |message| - errors.push(Error.new(page.name, view.path, message)) - end + page.errors.each do |message| + errors.push(Error.new(page.name, message)) end end end diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index c6ede07680a..62dd47bc0ad 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -13,7 +13,6 @@ module QA end validators.map(&:errors).flatten.tap do |errors| - end validators.each(&:validate!) -- cgit v1.2.1 From 44c8f919b4ba73fee6215ce2d6b023c666f34177 Mon Sep 17 00:00:00 2001 From: Benedikt Huss Date: Tue, 9 Jan 2018 11:40:09 +0100 Subject: Fix incorrect default merge request title when external issue tracker is activated --- app/services/merge_requests/build_service.rb | 2 +- .../unreleased/36669-default-mr-title-with-external-issues.yml | 5 +++++ spec/services/merge_requests/build_service_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/36669-default-mr-title-with-external-issues.yml diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 9622a5c5462..9781fecfb50 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -159,7 +159,7 @@ module MergeRequests merge_request.title = case issue when Issue then "Resolve \"#{issue.title}\"" - when ExternalIssue then "Resolve #{issue.title}" + when ExternalIssue then merge_request.source_branch.titleize.humanize end end diff --git a/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml b/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml new file mode 100644 index 00000000000..08523d01a72 --- /dev/null +++ b/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml @@ -0,0 +1,5 @@ +--- +title: Default merge request title is set correctly again when external issue tracker is activated +merge_request: +author: +type: fixed diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index a9605c6e4c6..4b670d3530e 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -241,8 +241,8 @@ describe MergeRequests::BuildService do allow(project).to receive(:external_issue_tracker).and_return(true) end - it 'sets the title to: Resolves External Issue $issue-iid' do - expect(merge_request.title).to eq('Resolve External Issue 12345') + it 'sets the title to the humanized branch title' do + expect(merge_request.title).to eq('12345 fix issue') end end end -- cgit v1.2.1 From e0edf6c7afbf103378c2e589212617df1510c55e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 12:34:47 +0100 Subject: Improve QA sanity selectors test output message --- qa/qa/page/base.rb | 2 +- qa/qa/page/element.rb | 4 ++++ qa/qa/page/validator.rb | 7 ++++++- qa/qa/scenario/test/sanity/selectors.rb | 28 ++++++++++++++++++++++++++++ qa/spec/page/base_spec.rb | 2 +- 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 05e300820fa..c60e9bfd40a 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -50,7 +50,7 @@ module QA def self.errors if views.empty? - return ["#{name} class does not have views / elements defined!"] + return ["Page class does not have views / elements defined!"] end views.map(&:errors).flatten diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 23e1b10fe33..791f92d002d 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -19,6 +19,10 @@ module QA def matches?(line) !!(line =~ expression) end + + def to_s + @name + end end end end diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb index 953a137ec34..e178fd9a1e9 100644 --- a/qa/qa/page/validator.rb +++ b/qa/qa/page/validator.rb @@ -2,7 +2,12 @@ module QA module Page class Validator ValidationError = Class.new(StandardError) - Error = Struct.new(:page, :message) + + Error = Struct.new(:page, :message) do + def to_s + "Error: #{page} - #{message}" + end + end def initialize(constant) @module = constant diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index 62dd47bc0ad..cb73b98da73 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -13,6 +13,34 @@ module QA end validators.map(&:errors).flatten.tap do |errors| + break if errors.none? + + STDERR.puts <<~EOS + GitLab QA sanity selectors validation test detected problems + your merge request! + + The purpose of this tes is to make sure that GitLab QA tests, + that are entirely black-box and click-driven scenario, do match + pages structure / layout in the GitLab CE / EE repositories. + + It looks like you have changed views / pages / selectors, and + these are now out of sync with what we have defined in `qa/` + directory. + + Please update code in `qa/` directory to match currect changes + in this merge request. + + For more help see documentation in `qa/page/README.md` file or + ask for help on #qa channel on Slack (GitLab Team only). + + If you are not a team member, and you still need help to + contribute, please open an issue in GitLab QA issue tracker. + + Please see errors described below. + + EOS + + STDERR.puts errors end validators.each(&:validate!) diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb index 29b62cf758c..287adf35c46 100644 --- a/qa/spec/page/base_spec.rb +++ b/qa/spec/page/base_spec.rb @@ -56,7 +56,7 @@ describe QA::Page::Base do it 'appends an error about missing views / elements block' do expect(described_class.errors) - .to include 'QA::Page::Base class does not have views / elements defined!' + .to include 'Page class does not have views / elements defined!' end end end -- cgit v1.2.1 From bd29d36ac6a1337aecf41ed91616b3fab4a5fd92 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 12:36:11 +0100 Subject: Add CI job that validates GitLab QA selectors --- .gitlab-ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4f47d3f0171..908646ecd60 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -623,6 +623,18 @@ qa:internal: - bundle install - bundle exec rspec +qa:selectors: + <<: *dedicated-runner + <<: *except-docs + stage: test + variables: + SETUP_DB: "false" + services: [] + script: + - cd qa/ + - bundle install + - bin/qa Test::Sanity::Selectors + coverage: <<: *dedicated-runner <<: *except-docs-and-qa -- cgit v1.2.1 From a171ceba104d29e482a55a0cf070bb1b54b82f07 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 12:37:34 +0100 Subject: Use array literal in QA page validation class --- qa/qa/page/validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb index e178fd9a1e9..117d8d4db67 100644 --- a/qa/qa/page/validator.rb +++ b/qa/qa/page/validator.rb @@ -33,7 +33,7 @@ module QA end def errors - Array.new.tap do |errors| + [].tap do |errors| descendants.each do |page| page.errors.each do |message| errors.push(Error.new(page.name, message)) -- cgit v1.2.1 From fce8f526b19a4b7d5b68cf0ff109b77bceaeeaf3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 12:45:17 +0100 Subject: Copy edit QA selectors sanity test failure message --- qa/qa/scenario/test/sanity/selectors.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index cb73b98da73..9f3a14b245a 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -19,21 +19,21 @@ module QA GitLab QA sanity selectors validation test detected problems your merge request! - The purpose of this tes is to make sure that GitLab QA tests, - that are entirely black-box and click-driven scenario, do match - pages structure / layout in the GitLab CE / EE repositories. + The purpose of this test is to make sure that GitLab QA tests, + that are entirely black-box, click-driven scenarios, do match + pages structure / layout in GitLab CE / EE repositories. It looks like you have changed views / pages / selectors, and these are now out of sync with what we have defined in `qa/` directory. - Please update code in `qa/` directory to match currect changes - in this merge request. + Please update the code in `qa/` directory to make it match + current changes in this merge request. For more help see documentation in `qa/page/README.md` file or ask for help on #qa channel on Slack (GitLab Team only). - If you are not a team member, and you still need help to + If you are not a Team Member, and you still need help to contribute, please open an issue in GitLab QA issue tracker. Please see errors described below. -- cgit v1.2.1 From df774fae4148fcb91380841c48514762d255affb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 13:07:06 +0100 Subject: Fux Rubocop offense by freezeing consts in QA test --- qa/qa/scenario/test/sanity/selectors.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index 9f3a14b245a..d42dfcfa364 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -5,10 +5,10 @@ module QA class Selectors < Scenario::Template include Scenario::Bootable - PAGE_MODULES = [QA::Page] + PAGES = [QA::Page].freeze def perform(*) - validators = PAGE_MODULES.map do |pages| + validators = PAGES.map do |pages| Page::Validator.new(pages) end -- cgit v1.2.1 From dee047aaca8e668a195194163e2b8841bf5dc647 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 13:20:57 +0100 Subject: Add QA login page views / selectors definition --- qa/qa/page/element.rb | 4 ---- qa/qa/page/main/login.rb | 12 ++++++++++++ qa/qa/page/view.rb | 4 ++-- qa/spec/page/view_spec.rb | 10 ++-------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 791f92d002d..23e1b10fe33 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -19,10 +19,6 @@ module QA def matches?(line) !!(line =~ expression) end - - def to_s - @name - end end end end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index f88325f408b..7b4c1603017 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -2,6 +2,18 @@ module QA module Page module Main class Login < Page::Base + view 'app/views/devise/passwords/edit.html.haml' do + element :password_field, 'password_field :password' + element :password_confirmation, 'password_field :password_confirmation' + element :change_password_button, 'submit "Change your password"' + end + + view 'app/views/devise/sessions/_new_base.html.haml' do + element :login_field, 'text_field :login' + element :passowrd_field, 'password_field :password' + element :sign_in_button, 'submit "Sign in"' + end + def initialize wait('.application', time: 500) end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index 47658144783..e8fba4dffd2 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -23,13 +23,13 @@ module QA # elements' existence. # @missing ||= @elements.dup.tap do |elements| - File.new(pathname.to_s).foreach do |line| + File.foreach(pathname.to_s) do |line| elements.reject! { |element| element.matches?(line) } end end @missing.map do |missing| - "Missing element `#{missing}` in `#{pathname}` view partial!" + "Missing element `#{missing.name}` in `#{pathname}` view partial!" end end diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb index dd38b171ad5..aedbc3863a7 100644 --- a/qa/spec/page/view_spec.rb +++ b/qa/spec/page/view_spec.rb @@ -24,12 +24,6 @@ describe QA::Page::View do end describe '#errors' do - let(:file) { spy('file') } - - before do - allow(File).to receive(:new).and_return(file) - end - context 'when view partial is present' do before do allow(subject.pathname).to receive(:readable?) @@ -38,7 +32,7 @@ describe QA::Page::View do context 'when pattern is found' do before do - allow(file).to receive(:foreach) + allow(File).to receive(:foreach) .and_yield('some element').once allow(element).to receive(:matches?) .with('some element').and_return(true) @@ -51,7 +45,7 @@ describe QA::Page::View do context 'when pattern has not been found' do before do - allow(file).to receive(:foreach) + allow(File).to receive(:foreach) .and_yield('some element').once allow(element).to receive(:matches?) .with('some element').and_return(false) -- cgit v1.2.1 From 8c22c65425a56525b80a6a998e40ce07946ff96f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 13:51:07 +0100 Subject: Add QA page views / elements couling with the menu --- app/views/dashboard/_projects_head.html.haml | 2 +- app/views/layouts/header/_default.html.haml | 4 ++-- app/views/layouts/nav/_dashboard.html.haml | 6 +++--- qa/qa/page/base.rb | 4 ++++ qa/qa/page/main/oauth.rb | 4 ++++ qa/qa/page/menu/main.rb | 31 +++++++++++++++++++++------- 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 9038c4fbebd..cc636d622a1 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -6,7 +6,7 @@ .fade-right= icon('angle-right') %ul.nav-links.scrolling-tabs = nav_link(page: [dashboard_projects_path, root_path]) do - = link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do + = link_to dashboard_projects_path, class: 'shortcuts-activity qa-your-projects-link', data: {placement: 'right'} do Your projects = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, data: {placement: 'right'} do diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 39eb71c2bac..5b1c5689ba6 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,4 +1,4 @@ -%header.navbar.navbar-gitlab +%header.navbar.navbar-gitlab.qa-navbar %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content .container-fluid .header-content @@ -43,7 +43,7 @@ = todos_count_format(todos_pending_count) %li.header-user.dropdown = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do - = image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar" + = image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" = sprite_icon('angle-down', css_class: 'caret-down') .dropdown-menu-nav.dropdown-menu-align-right %ul diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 4013181da9c..ed82623eada 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,12 +1,12 @@ %ul.list-unstyled.navbar-sub-nav - = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects" }) do + = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown" }) do %a{ href: "#", data: { toggle: "dropdown" } } Projects = sprite_icon('angle-down', css_class: 'caret-down') .dropdown-menu.projects-dropdown-menu = render "layouts/nav/projects_dropdown/show" - = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "hidden-xs" }) do + = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "hidden-xs qa-groups-link" }) do = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do Groups @@ -59,7 +59,7 @@ %li.line-separator.hidden-xs - if current_user.admin? = nav_link(controller: 'admin/dashboard') do - = link_to admin_root_path, class: 'admin-icon', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('admin', size: 18) - if Gitlab::Sherlock.enabled? %li diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index c60e9bfd40a..d1641565bad 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -40,6 +40,10 @@ module QA page.within(selector) { yield } if block_given? end + def click_element(name) + find("qa-#{name.tr('_', '-')}").click + end + def self.path raise NotImplementedError end diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb index e746cff0a80..6f548148363 100644 --- a/qa/qa/page/main/oauth.rb +++ b/qa/qa/page/main/oauth.rb @@ -2,6 +2,10 @@ module QA module Page module Main class OAuth < Page::Base + view 'app/views/doorkeeper/authorizations/new.html.haml' do + element :authorization_button, 'submit_tag "Authorize"' + end + def needs_authorization? page.current_url.include?('/oauth') end diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb index b94c2c6c23d..36172151969 100644 --- a/qa/qa/page/menu/main.rb +++ b/qa/qa/page/menu/main.rb @@ -2,19 +2,36 @@ module QA module Page module Menu class Main < Page::Base + view 'app/views/layouts/header/_default.html.haml' do + element :navbar + element :user_avatar + element :user_menu, '.dropdown-menu-nav' + element :user_sign_out_link, 'link_to "Sign out"' + end + + view 'app/views/layouts/nav/_dashboard.html.haml' do + element :admin_area_link + element :projects_dropdown + element :groups_link + end + + view 'app/views/dashboard/_projects_head.html.haml' do + element :your_projects_link + end + def go_to_groups - within_top_menu { click_link 'Groups' } + within_top_menu { click_element :groups_link } end def go_to_projects within_top_menu do - click_link 'Projects' - click_link 'Your projects' + click_element :projects_dropdown + click_element :your_projects_link end end def go_to_admin_area - within_top_menu { find('.admin-icon').click } + within_top_menu { click_element :admin_area_link } end def sign_out @@ -24,20 +41,20 @@ module QA end def has_personal_area? - page.has_selector?('.header-user-dropdown-toggle') + page.has_selector?('.qa-user-avatar') end private def within_top_menu - page.within('.navbar') do + page.within('.qa-navbar') do yield end end def within_user_menu within_top_menu do - find('.header-user-dropdown-toggle').click + click_element :user_avatar page.within('.dropdown-menu-nav') do yield -- cgit v1.2.1 From 65615776ebe67f0da57d27722dbcbf6f2649f81d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 13:55:03 +0100 Subject: Allow failure of qa:selectors job for now --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 908646ecd60..70342691bfc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -630,10 +630,11 @@ qa:selectors: variables: SETUP_DB: "false" services: [] + allow_failure: true script: - cd qa/ - bundle install - - bin/qa Test::Sanity::Selectors + - bundle exec bin/qa Test::Sanity::Selectors coverage: <<: *dedicated-runner -- cgit v1.2.1 From 7725a7071d9ff2a32b9dfa9c19ac827e2e56fd9b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 14:28:10 +0100 Subject: Reduce duplication in QA page elements-related code --- qa/qa/page/base.rb | 2 +- qa/qa/page/element.rb | 6 +++++- qa/qa/scenario/test/sanity/selectors.rb | 2 +- qa/spec/page/element_spec.rb | 7 +++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index d1641565bad..3f409f1ba03 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -41,7 +41,7 @@ module QA end def click_element(name) - find("qa-#{name.tr('_', '-')}").click + find(Page::Element.new(name).selector).click end def self.path diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 23e1b10fe33..d8db40b1b5d 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -5,7 +5,11 @@ module QA def initialize(name, pattern = nil) @name = name - @pattern = pattern || "qa-#{@name.to_s.tr('_', '-')}" + @pattern = pattern || selector + end + + def selector + "qa-#{@name.to_s.tr('_', '-')}" end def expression diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index d42dfcfa364..365eb3315f8 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -17,7 +17,7 @@ module QA STDERR.puts <<~EOS GitLab QA sanity selectors validation test detected problems - your merge request! + with your merge request! The purpose of this test is to make sure that GitLab QA tests, that are entirely black-box, click-driven scenarios, do match diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index c665267b90e..a48f1f54a32 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -1,4 +1,11 @@ describe QA::Page::Element do + describe '#selector' do + it 'transform element name into QA-specific selector' do + expect(described_class.new(:sign_in_button).selector) + .to eq 'qa-sign-in-button' + end + end + context 'when pattern is an expression' do subject { described_class.new(:something, /button 'Sign in'/) } -- cgit v1.2.1 From ffd109af4ef74ce54203f4067c2be3d2ff31e137 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 14:36:09 +0100 Subject: Add tests for QA test selectors sanity scenario --- qa/qa/scenario/test/sanity/selectors.rb | 4 +-- qa/spec/scenario/test/sanity/selectors_spec.rb | 40 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 qa/spec/scenario/test/sanity/selectors_spec.rb diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index 365eb3315f8..157336be66f 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -15,7 +15,7 @@ module QA validators.map(&:errors).flatten.tap do |errors| break if errors.none? - STDERR.puts <<~EOS + $stderr.puts <<~EOS GitLab QA sanity selectors validation test detected problems with your merge request! @@ -40,7 +40,7 @@ module QA EOS - STDERR.puts errors + $stderr.puts errors end validators.each(&:validate!) diff --git a/qa/spec/scenario/test/sanity/selectors_spec.rb b/qa/spec/scenario/test/sanity/selectors_spec.rb new file mode 100644 index 00000000000..a64a60fa119 --- /dev/null +++ b/qa/spec/scenario/test/sanity/selectors_spec.rb @@ -0,0 +1,40 @@ +describe QA::Scenario::Test::Sanity::Selectors do + let(:validator) { spy('validator') } + + before do + stub_const('QA::Page::Validator', validator) + end + + context 'when there are errors detected' do + before do + allow(validator).to receive(:errors).and_return(['some error']) + end + + it 'outputs information about errors' do + expect { described_class.perform } + .to output(/some error/).to_stderr + + expect { described_class.perform } + .to output(/electors validation test detected problems/) + .to_stderr + end + end + + context 'when there are no errors detected' do + before do + allow(validator).to receive(:errors).and_return([]) + end + + it 'processes pages module' do + described_class.perform + + expect(validator).to have_received(:new).with(QA::Page) + end + + it 'triggers validation' do + described_class.perform + + expect(validator).to have_received(:validate!) + end + end +end -- cgit v1.2.1 From b91b6418158e1e70d9d101b0cf82d92740fe2faf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 14:53:35 +0100 Subject: Add QA selectors coupling with sidebar menu --- app/views/layouts/nav/sidebar/_project.html.haml | 2 +- qa/qa/page/menu/side.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 1fa3a3041fd..abd07d71bcc 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -226,7 +226,7 @@ = link_to edit_project_path(@project), class: 'shortcuts-tree' do .nav-icon-container = sprite_icon('settings') - %span.nav-item-name + %span.nav-item-name.qa-settings-item Settings %ul.sidebar-sub-level-items diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 6c25aba4bac..1df4e0c2429 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -2,6 +2,12 @@ module QA module Page module Menu class Side < Page::Base + view 'app/views/layouts/nav/sidebar/_project.html.haml' do + element :settings_item + element :repository_link, "title: 'Repository'" + element :top_level_items, '.sidebar-top-level-items' + end + def click_repository_setting hover_setting do click_link('Repository') @@ -12,7 +18,7 @@ module QA def hover_setting within_sidebar do - find('.nav-item-name', text: 'Settings').hover + find('.qa-settings-item').hover yield end -- cgit v1.2.1 From fc9ecdbb4f98e91d01ad73edcd3d659dfed7b9bf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 9 Jan 2018 15:12:15 +0100 Subject: Add QA views / selectors coupling to group/project pages --- qa/qa/page/dashboard/groups.rb | 9 +++++++++ qa/qa/page/dashboard/projects.rb | 2 ++ qa/qa/page/group/new.rb | 11 +++++++++++ qa/qa/page/view.rb | 2 +- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb index 083d2e1ab16..e853e0d85e0 100644 --- a/qa/qa/page/dashboard/groups.rb +++ b/qa/qa/page/dashboard/groups.rb @@ -2,6 +2,15 @@ module QA module Page module Dashboard class Groups < Page::Base + view 'app/views/shared/groups/_search_form.html.haml' do + element :groups_filter, 'search_field_tag :filter' + element :groups_filter_placeholder, 'Filter by name...' + end + + view 'app/views/dashboard/_groups_head.html.haml' do + element :new_group_button, 'link_to _("New group")' + end + def filter_by_name(name) fill_in 'Filter by name...', with: name end diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb index 7ed27da6d89..71255b18362 100644 --- a/qa/qa/page/dashboard/projects.rb +++ b/qa/qa/page/dashboard/projects.rb @@ -2,6 +2,8 @@ module QA module Page module Dashboard class Projects < Page::Base + view 'app/views/dashboard/projects/index.html.haml' + def go_to_project(name) find_link(text: name).click end diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb index 53fdaaed078..48b71a7c883 100644 --- a/qa/qa/page/group/new.rb +++ b/qa/qa/page/group/new.rb @@ -2,6 +2,17 @@ module QA module Page module Group class New < Page::Base + view 'app/views/shared/_group_form.html.haml' do + element :group_path_field, 'text_field :path' + element :group_name_field, 'text_field :name' + element :group_description_field, 'text_area :description' + end + + view 'app/views/groups/new.html.haml' do + element :create_group_button, "submit 'Create group'" + element :visibility_radios, 'visibility_level:' + end + def set_path(path) fill_in 'group_path', with: path fill_in 'group_name', with: path diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index e8fba4dffd2..ebb28d5fb12 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -35,7 +35,7 @@ module QA def self.evaluate(&block) Page::View::DSL.new.tap do |evaluator| - evaluator.instance_exec(&block) + evaluator.instance_exec(&block) if block_given? end end -- cgit v1.2.1 From 657065b73eb7253d60c9a63c74a75c0f9c3085ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20=22BKC=22=20Carlb=C3=A4cker?= Date: Mon, 8 Jan 2018 16:24:59 +0100 Subject: Client-prep Gitlab::Git::Repository#write_ref --- app/models/project.rb | 2 +- app/models/repository.rb | 6 +----- lib/gitlab/git/repository.rb | 17 +++++++++++++++-- spec/models/project_spec.rb | 5 ++--- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index fbe65e700a4..7dc5e980c1b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1149,7 +1149,7 @@ class Project < ActiveRecord::Base def change_head(branch) if repository.branch_exists?(branch) repository.before_change_head - repository.write_ref('HEAD', "refs/heads/#{branch}") + repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}", shell: false) repository.copy_gitattributes(branch) repository.after_change_head reload_default_branch diff --git a/app/models/repository.rb b/app/models/repository.rb index 9c879e2006b..5fefdb5ef61 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -256,7 +256,7 @@ class Repository # This will still fail if the file is corrupted (e.g. 0 bytes) begin - write_ref(keep_around_ref_name(sha), sha) + raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false) rescue Rugged::ReferenceError => ex Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" rescue Rugged::OSError => ex @@ -270,10 +270,6 @@ class Repository ref_exists?(keep_around_ref_name(sha)) end - def write_ref(ref_path, sha) - rugged.references.create(ref_path, sha, force: true) - end - def diverging_commit_counts(branch) root_ref_hash = raw_repository.commit(root_ref).id cache.fetch(:"diverging_commit_counts_#{branch.name}") do diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 283134e043e..fa9bc57dd79 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1103,14 +1103,27 @@ module Gitlab end end - def write_ref(ref_path, ref) + def write_ref(ref_path, ref, old_ref: nil, shell: true) + if shell + shell_write_ref(ref_path, ref, old_ref) + else + rugged_write_ref(ref_path, ref) + end + end + + def shell_write_ref(ref_path, ref, old_ref) raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ') raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00") + raise ArgumentError, "invalid old_ref #{old_ref.inspect}" if !old_ref.nil? && old_ref.include?("\x00") - input = "update #{ref_path}\x00#{ref}\x00\x00" + input = "update #{ref_path}\x00#{ref}\x00#{old_ref}\x00" run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) } end + def rugged_write_ref(ref_path, ref) + rugged.references.create(ref_path, ref, force: true) + end + def fetch_ref(source_repository, source_ref:, target_ref:) Gitlab::Git.check_namespace!(source_repository) source_repository = RemoteRepository.new(source_repository) unless source_repository.is_a?(RemoteRepository) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 00afa09f1a3..78223c44999 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1871,9 +1871,8 @@ describe Project do end it 'creates the new reference with rugged' do - expect(project.repository.rugged.references).to receive(:create).with('HEAD', - "refs/heads/#{project.default_branch}", - force: true) + expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', "refs/heads/#{project.default_branch}", shell: false) + project.change_head(project.default_branch) end -- cgit v1.2.1 From d805cd3606f21c504e34f7266c1100d688ea1cf5 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 9 Jan 2018 16:59:46 +0100 Subject: Add option to disable git archive caching in workhorse --- GITLAB_WORKHORSE_VERSION | 2 +- lib/gitlab/workhorse.rb | 7 +++++++ spec/lib/gitlab/workhorse_spec.rb | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 18091983f59..1545d966571 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -3.4.0 +3.5.0 diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 5ab6cd5a4ef..ce6d0422c1f 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -97,6 +97,9 @@ module Gitlab ) end + # If present DisableCache must be a Boolean. Otherwise workhorse ignores it. + params['DisableCache'] = true if git_archive_cache_disabled? + [ SEND_DATA_HEADER, "git-archive:#{encode(params)}" @@ -244,6 +247,10 @@ module Gitlab right_commit_id: diff_refs.head_sha } end + + def git_archive_cache_disabled? + ENV['WORKHORSE_ARCHIVE_CACHE_DISABLED'].present? || Feature.enabled?(:workhorse_archive_cache_disabled) + end end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 249c77dc636..2e7a0265a0b 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -26,11 +26,16 @@ describe Gitlab::Workhorse do 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys ) end + let(:cache_disabled) { false } subject do described_class.send_git_archive(repository, ref: ref, format: format) end + before do + allow(described_class).to receive(:git_archive_cache_disabled?).and_return(cache_disabled) + end + context 'when Gitaly workhorse_archive feature is enabled' do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) @@ -39,6 +44,15 @@ describe Gitlab::Workhorse do expect(command).to eq('git-archive') expect(params).to include(gitaly_params) end + + context 'when archive caching is disabled' do + let(:cache_disabled) { true } + + it 'tells workhorse not to use the cache' do + _, _, params = decode_workhorse_header(subject) + expect(params).to include({ 'DisableCache' => true }) + end + end end context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do -- cgit v1.2.1 From a560f785f7f34b932c285365790a27d15bd100ec Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 9 Jan 2018 16:49:39 +0100 Subject: Store only generic message if rebase fails Instead of storing detailed rebase error, only a generic message is stored with MR. The reason is that this message is exposed and displayed to end user and there is no reason to expose detailed backend information. Error message is still logged so detailed information can be found in logfile by admin if needed. Related #41820 --- app/services/merge_requests/rebase_service.rb | 8 +++++--- changelogs/unreleased/4020-rebase-message.yml | 5 +++++ spec/services/merge_requests/rebase_service_spec.rb | 14 +++++++------- 3 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/4020-rebase-message.yml diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb index 0d5a25fa28e..c0083cd6afd 100644 --- a/app/services/merge_requests/rebase_service.rb +++ b/app/services/merge_requests/rebase_service.rb @@ -1,12 +1,14 @@ module MergeRequests class RebaseService < MergeRequests::WorkingCopyBaseService + REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze + def execute(merge_request) @merge_request = merge_request if rebase success else - error('Failed to rebase. Should be done manually') + error(REBASE_ERROR) end end @@ -22,8 +24,8 @@ module MergeRequests true rescue => e - log_error('Failed to rebase branch:') - log_error(e.message, save_message_on_model: true) + log_error(REBASE_ERROR, save_message_on_model: true) + log_error(e.message) false end end diff --git a/changelogs/unreleased/4020-rebase-message.yml b/changelogs/unreleased/4020-rebase-message.yml new file mode 100644 index 00000000000..4793f3d9cb9 --- /dev/null +++ b/changelogs/unreleased/4020-rebase-message.yml @@ -0,0 +1,5 @@ +--- +title: Display user friendly error message if rebase fails. +merge_request: +author: +type: fixed diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index d1b37cdd073..5f047e61c31 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -32,7 +32,7 @@ describe MergeRequests::RebaseService do it 'returns an error' do expect(service.execute(merge_request)).to match(status: :error, - message: 'Failed to rebase. Should be done manually') + message: described_class::REBASE_ERROR) end end @@ -41,15 +41,15 @@ describe MergeRequests::RebaseService do allow(repository).to receive(:run_git!).and_raise('Something went wrong') end - it 'saves the error message' do + it 'saves a generic error message' do subject.execute(merge_request) - expect(merge_request.reload.merge_error).to eq 'Something went wrong' + expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR end it 'returns an error' do expect(service.execute(merge_request)).to match(status: :error, - message: 'Failed to rebase. Should be done manually') + message: described_class::REBASE_ERROR) end end @@ -58,15 +58,15 @@ describe MergeRequests::RebaseService do allow(repository).to receive(:run_git!).and_raise(Gitlab::Git::Repository::GitError, 'Something went wrong') end - it 'saves the error message' do + it 'saves a generic error message' do subject.execute(merge_request) - expect(merge_request.reload.merge_error).to eq 'Something went wrong' + expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR end it 'returns an error' do expect(service.execute(merge_request)).to match(status: :error, - message: 'Failed to rebase. Should be done manually') + message: described_class::REBASE_ERROR) end end -- cgit v1.2.1 From 5d58766fdca12fc1db0d99a3807d89f2f229ea68 Mon Sep 17 00:00:00 2001 From: Constance Okoghenun Date: Tue, 9 Jan 2018 18:36:10 +0100 Subject: Refactored profile:* and help:* imports in dispatcher --- app/assets/javascripts/dispatcher.js | 17 +++++++++++------ app/assets/javascripts/pages/help/index.js | 3 +++ app/assets/javascripts/pages/profiles/index/index.js | 7 +++++++ .../pages/profiles/personal_access_tokens/index.js | 3 +++ .../javascripts/pages/profiles/preferences/index.js | 3 +++ 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/pages/help/index.js create mode 100644 app/assets/javascripts/pages/profiles/index/index.js create mode 100644 app/assets/javascripts/pages/profiles/personal_access_tokens/index.js create mode 100644 app/assets/javascripts/pages/profiles/preferences/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 9e8b2acfe1b..40c4a563db9 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -59,11 +59,9 @@ import Star from './star'; import TreeView from './tree'; import UsagePing from './usage_ping'; import UsernameValidator from './username_validator'; -import VersionCheckImage from './version_check_image'; import Wikis from './wikis'; import ZenMode from './zen_mode'; import initSettingsPanels from './settings_panels'; -import initExperimentalFlags from './experimental_flags'; import OAuthRememberMe from './oauth_remember_me'; import PerformanceBar from './performance_bar'; import initBroadcastMessagesForm from './broadcast_message'; @@ -159,7 +157,9 @@ import Activities from './activities'; switch (page) { case 'profiles:preferences:show': - initExperimentalFlags(); + import('./pages/profiles/preferences') + .then(callDefault) + .catch(fail); break; case 'sessions:new': new UsernameValidator(); @@ -512,7 +512,7 @@ import Activities from './activities'; new BlobViewer(); break; case 'help:index': - VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); + import('./pages/help').then(module => module.default()).catch(fail); break; case 'search:show': new Search(); @@ -557,6 +557,10 @@ import Activities from './activities'; new UsersSelect(); break; case 'profiles:personal_access_tokens:index': + import('./pages/profiles/personal_access_tokens') + .then(callDefault) + .catch(fail); + break; case 'admin:impersonation_tokens:index': new DueDateSelectors(); break; @@ -623,8 +627,9 @@ import Activities from './activities'; new UserCallout(); break; case 'profiles': - new NotificationsForm(); - notificationsDropdown(); + import('./pages/profiles/index/') + .then(callDefault) + .catch(fail); break; case 'projects': new Project(); diff --git a/app/assets/javascripts/pages/help/index.js b/app/assets/javascripts/pages/help/index.js new file mode 100644 index 00000000000..4cf8afc4b7e --- /dev/null +++ b/app/assets/javascripts/pages/help/index.js @@ -0,0 +1,3 @@ +import VersionCheckImage from '../../version_check_image'; + +export default () => VersionCheckImage.bindErrorEvent($('img.js-version-status-badge')); diff --git a/app/assets/javascripts/pages/profiles/index/index.js b/app/assets/javascripts/pages/profiles/index/index.js new file mode 100644 index 00000000000..90eed38777a --- /dev/null +++ b/app/assets/javascripts/pages/profiles/index/index.js @@ -0,0 +1,7 @@ +import NotificationsForm from '../../../notifications_form'; +import notificationsDropdown from '../../../notifications_dropdown'; + +export default () => { + new NotificationsForm(); // eslint-disable-line no-new + notificationsDropdown(); +}; diff --git a/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js b/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js new file mode 100644 index 00000000000..030328a1363 --- /dev/null +++ b/app/assets/javascripts/pages/profiles/personal_access_tokens/index.js @@ -0,0 +1,3 @@ +import DueDateSelectors from '../../../due_date_select'; + +export default () => new DueDateSelectors(); diff --git a/app/assets/javascripts/pages/profiles/preferences/index.js b/app/assets/javascripts/pages/profiles/preferences/index.js new file mode 100644 index 00000000000..bc399bb7138 --- /dev/null +++ b/app/assets/javascripts/pages/profiles/preferences/index.js @@ -0,0 +1,3 @@ +import initExperimentalFlags from '../../../experimental_flags'; + +export default () => initExperimentalFlags(); -- cgit v1.2.1 From 7034ef29edd853b3d18d3666a19826217990bfb1 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Tue, 9 Jan 2018 18:00:11 +0000 Subject: Hide new branch and tag links for projects with an empty repo --- app/views/projects/buttons/_dropdown.html.haml | 13 ++++---- .../39988-hide-new-branch-tag-empty-repo.yml | 5 +++ .../projects/buttons/_dropdown.html.haml_spec.rb | 39 ++++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml create mode 100644 spec/views/projects/buttons/_dropdown.html.haml_spec.rb diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 2589c53beae..8e8c911185a 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -30,12 +30,13 @@ %li = link_to project_new_blob_path(@project, @project.default_branch || 'master') do #{ _('New file') } - %li - = link_to new_project_branch_path(@project) do - #{ _('New branch') } - %li - = link_to new_project_tag_path(@project) do - #{ _('New tag') } + - unless @project.empty_repo? + %li + = link_to new_project_branch_path(@project) do + #{ _('New branch') } + %li + = link_to new_project_tag_path(@project) do + #{ _('New tag') } - elsif current_user && current_user.already_forked?(@project) %li = link_to project_new_blob_path(@project, @project.default_branch || 'master') do diff --git a/changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml b/changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml new file mode 100644 index 00000000000..4f2c87c44b3 --- /dev/null +++ b/changelogs/unreleased/39988-hide-new-branch-tag-empty-repo.yml @@ -0,0 +1,5 @@ +--- +title: Hide new branch and tag links for projects with an empty repo +merge_request: +author: +type: fixed diff --git a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb new file mode 100644 index 00000000000..d0e692635b9 --- /dev/null +++ b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe 'projects/buttons/_dropdown' do + let(:user) { create(:user) } + + context 'user with all abilities' do + before do + assign(:project, project) + + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:can?).and_return(true) + end + + context 'empty repository' do + let(:project) { create(:project, :empty_repo) } + + it 'has a link to create a new file' do + render + + expect(view).to render_template('projects/buttons/_dropdown') + expect(rendered).to have_link('New file') + end + + it 'does not have a link to create a new branch' do + render + + expect(view).to render_template('projects/buttons/_dropdown') + expect(rendered).not_to have_link('New branch') + end + + it 'does not have a link to create a new tag' do + render + + expect(view).to render_template('projects/buttons/_dropdown') + expect(rendered).not_to have_link('New tag') + end + end + end +end -- cgit v1.2.1 From 80b8fe24a18151eae7262589432a3158354c7273 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 9 Jan 2018 18:34:44 -0200 Subject: Fix small typos --- doc/administration/raketasks/check.md | 6 +++--- lib/tasks/gitlab/uploads.rake | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index 7e452ae503d..94d71e32ef9 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -79,10 +79,10 @@ Example output: ## Uploaded Files Integrity The uploads check Rake task will loop through all uploads in the database -and runs two checks to determine the integrity of each file: +and run two checks to determine the integrity of each file: -1. Check if the file exist in the file system. -1. Check if the checksum of the file in the file system matches the checksum in the database. +1. Check if the file exist on the file system. +1. Check if the checksum of the file on the file system matches the checksum in the database. **Omnibus Installation** diff --git a/lib/tasks/gitlab/uploads.rake b/lib/tasks/gitlab/uploads.rake index fd5ef78103b..df31567ce64 100644 --- a/lib/tasks/gitlab/uploads.rake +++ b/lib/tasks/gitlab/uploads.rake @@ -2,7 +2,7 @@ namespace :gitlab do namespace :uploads do desc 'GitLab | Uploads | Check integrity of uploaded files' task check: :environment do - puts 'Starting checking integrity of uploaded files' + puts 'Checking integrity of uploaded files' uploads_batches do |batch| batch.each do |upload| -- cgit v1.2.1 From 1faaf6ffbc7ccf8dd12245a7003de9d91088d11c Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 4 Jan 2018 12:54:34 -0600 Subject: Fix 500 when visiting a commit where blobs do not exist (nil blobs) Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/41491 --- app/assets/stylesheets/pages/diff.scss | 8 +++++++- app/views/projects/diffs/_file.html.haml | 2 +- app/views/projects/diffs/_stats.html.haml | 7 ++++++- changelogs/unreleased/41491-fix-nil-blob-name-error.yml | 5 +++++ .../projects/commits/user_browses_commits_spec.rb | 15 ++++++++++++++- 5 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/41491-fix-nil-blob-name-error.yml diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1d081b58f62..7f037582ca0 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -651,12 +651,18 @@ min-width: 0; } - .diff-changed-file-name { + .diff-changed-file-name, + .diff-changed-blank-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .diff-changed-blank-file-name { + color: $gl-text-color-tertiary; + font-style: italic; + } + .diff-changed-file-path { color: $gl-text-color-tertiary; } diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index adc4dcbed33..0b01e38d23d 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -11,7 +11,7 @@ - unless diff_file.submodule? - blob = diff_file.blob .file-actions.hidden-xs - - if blob.readable_text? + - if blob&.readable_text? = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 325159dd9a7..b082ad0ef0e 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -24,7 +24,12 @@ %a.diff-changed-file{ href: "##{hexdigest(diff_file.file_path)}", title: diff_file.new_path } = sprite_icon(diff_file_changed_icon(diff_file), size: 16, css_class: "#{diff_file_changed_icon_color(diff_file)} diff-file-changed-icon append-right-8") %span.diff-changed-file-content.append-right-8 - %strong.diff-changed-file-name= diff_file.blob.name + - if diff_file.blob&.name + %strong.diff-changed-file-name + = diff_file.blob.name + - else + %strong.diff-changed-blank-file-name + = s_('Diffs|No file name available') %span.diff-changed-file-path.prepend-top-5= diff_file_path_text(diff_file) %span.diff-changed-stats %span.cgreen< diff --git a/changelogs/unreleased/41491-fix-nil-blob-name-error.yml b/changelogs/unreleased/41491-fix-nil-blob-name-error.yml new file mode 100644 index 00000000000..cf7e63ea46a --- /dev/null +++ b/changelogs/unreleased/41491-fix-nil-blob-name-error.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 error when visiting a commit where the blobs do not exist +merge_request: +author: +type: fixed diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index 41f3c15a94c..b650c1f4197 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'User broweses commits' do +describe 'User browses commits' do let(:user) { create(:user) } let(:project) { create(:project, :repository, namespace: user.namespace) } @@ -31,6 +31,19 @@ describe 'User broweses commits' do check_author_link(RepoHelpers.sample_commit.author_email, user) end end + + context 'when the blob does not exist' do + let(:commit) { create(:commit, project: project) } + + it 'shows a blank label' do + allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil) + allow_any_instance_of(Gitlab::Diff::File).to receive(:raw_binary?).and_return(true) + + visit(project_commit_path(project, commit)) + + expect(find('.diff-file-changes', visible: false)).to have_content('No file name available') + end + end end private -- cgit v1.2.1 From d270693c86beda5c44ec4e5d842d81961a8809e6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 9 Jan 2018 22:02:44 -0200 Subject: Add docs for ENV variables take gitlab:uploads:check rake task supports --- doc/administration/raketasks/check.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index 94d71e32ef9..d1ed152b58c 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -96,6 +96,19 @@ sudo gitlab-rake gitlab:uploads:check sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production ``` +This task also accepts some environment variables which you can use to override +certain values: + +Variable | Type | Description +-------- | ---- | ----------- +`BATCH` | integer | Specifies the size of the batch. Defaults to 200. +`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value. +`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value. + +```bash +sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250 +``` + ## LDAP Check The LDAP check Rake task will test the bind_dn and password credentials -- cgit v1.2.1 From c415c6204730c7aa3acdf740f96c8a407e831d82 Mon Sep 17 00:00:00 2001 From: John Doe Date: Wed, 10 Jan 2018 08:50:34 +0100 Subject: Exclude sast job from being run in docs only changes --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4f47d3f0171..22de5a5a2c5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -604,6 +604,7 @@ codequality: paths: [codeclimate.json] sast: + <<: *except-docs image: registry.gitlab.com/gitlab-org/gl-sast:latest before_script: [] script: -- cgit v1.2.1 From 9910968f0c3a630b9ac04b9691b4fba51619e2d3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 10:49:11 +0100 Subject: Fix QA selector class used to click on QA elements --- app/views/layouts/nav/_dashboard.html.haml | 4 ++-- qa/qa/page/base.rb | 2 +- qa/qa/page/element.rb | 4 ++++ qa/spec/page/element_spec.rb | 9 ++++++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index ed82623eada..74532eba298 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -6,8 +6,8 @@ .dropdown-menu.projects-dropdown-menu = render "layouts/nav/projects_dropdown/show" - = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "hidden-xs qa-groups-link" }) do - = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do + = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "hidden-xs" }) do + = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups qa-groups-link', title: 'Groups' do Groups = nav_link(path: 'dashboard#activity', html_options: { class: "visible-lg" }) do diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 3f409f1ba03..ea4c920c82c 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -41,7 +41,7 @@ module QA end def click_element(name) - find(Page::Element.new(name).selector).click + find(Page::Element.new(name).selector_css).click end def self.path diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index d8db40b1b5d..9944a39ce07 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -12,6 +12,10 @@ module QA "qa-#{@name.to_s.tr('_', '-')}" end + def selector_css + ".#{selector}" + end + def expression if @pattern.is_a?(String) @_regexp ||= Regexp.new(Regexp.escape(@pattern)) diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index a48f1f54a32..8598c57ad34 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -1,11 +1,18 @@ describe QA::Page::Element do describe '#selector' do - it 'transform element name into QA-specific selector' do + it 'transforms element name into QA-specific selector' do expect(described_class.new(:sign_in_button).selector) .to eq 'qa-sign-in-button' end end + describe '#selector_css' do + it 'transforms element name into QA-specific clickable css selector' do + expect(described_class.new(:sign_in_button).selector_css) + .to eq '.qa-sign-in-button' + end + end + context 'when pattern is an expression' do subject { described_class.new(:something, /button 'Sign in'/) } -- cgit v1.2.1 From 7a94787fc93b5be13388e4e1aeeaf4f8773446c5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 10:50:37 +0100 Subject: Use `warn` instead of `$stderr.puts` in QA selectors test --- qa/qa/scenario/test/sanity/selectors.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index 157336be66f..5a8f368f88c 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -15,7 +15,7 @@ module QA validators.map(&:errors).flatten.tap do |errors| break if errors.none? - $stderr.puts <<~EOS + warn <<~EOS GitLab QA sanity selectors validation test detected problems with your merge request! @@ -40,7 +40,7 @@ module QA EOS - $stderr.puts errors + warn errors end validators.each(&:validate!) -- cgit v1.2.1 From b1c388706c14fa2edb6eaa2f9daa522297f288e3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 11:13:30 +0100 Subject: Add temporary views coupling to all QA page objects --- qa/qa/page/admin/settings.rb | 7 +++++++ qa/qa/page/group/show.rb | 7 +++++++ qa/qa/page/mattermost/login.rb | 7 +++++++ qa/qa/page/mattermost/main.rb | 7 +++++++ qa/qa/page/menu/admin.rb | 7 +++++++ qa/qa/page/project/new.rb | 7 +++++++ qa/qa/page/project/settings/deploy_keys.rb | 7 +++++++ qa/qa/page/project/settings/repository.rb | 7 +++++++ qa/qa/page/project/show.rb | 7 +++++++ qa/qa/scenario/test/sanity/selectors.rb | 2 ++ 10 files changed, 65 insertions(+) diff --git a/qa/qa/page/admin/settings.rb b/qa/qa/page/admin/settings.rb index 39e2f2062ad..1904732aee6 100644 --- a/qa/qa/page/admin/settings.rb +++ b/qa/qa/page/admin/settings.rb @@ -2,6 +2,13 @@ module QA module Page module Admin class Settings < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/admin/application_settings/show.html.haml' + def enable_hashed_storage scroll_to 'legend', text: 'Repository Storage' check 'Create new projects using hashed storage paths' diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb index 0a16c07d64b..37ed3b35bce 100644 --- a/qa/qa/page/group/show.rb +++ b/qa/qa/page/group/show.rb @@ -2,6 +2,13 @@ module QA module Page module Group class Show < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/groups/show.html.haml' + def go_to_subgroup(name) click_link name end diff --git a/qa/qa/page/mattermost/login.rb b/qa/qa/page/mattermost/login.rb index 8ffd4fdad13..9b21300ea3c 100644 --- a/qa/qa/page/mattermost/login.rb +++ b/qa/qa/page/mattermost/login.rb @@ -2,6 +2,13 @@ module QA module Page module Mattermost class Login < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/projects/mattermosts/new.html.haml' + def sign_in_using_oauth click_link class: 'btn btn-custom-login gitlab' diff --git a/qa/qa/page/mattermost/main.rb b/qa/qa/page/mattermost/main.rb index 4b8fc28e53f..bc2f9acc729 100644 --- a/qa/qa/page/mattermost/main.rb +++ b/qa/qa/page/mattermost/main.rb @@ -2,6 +2,13 @@ module QA module Page module Mattermost class Main < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/projects/mattermosts/new.html.haml' + def initialize visit(Runtime::Scenario.mattermost_address) end diff --git a/qa/qa/page/menu/admin.rb b/qa/qa/page/menu/admin.rb index 07fe40fda3a..40da4a53e8a 100644 --- a/qa/qa/page/menu/admin.rb +++ b/qa/qa/page/menu/admin.rb @@ -2,6 +2,13 @@ module QA module Page module Menu class Admin < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/admin/dashboard/index.html.haml' + def go_to_license click_link 'License' end diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index b31bec27b59..a87313b2cb9 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -2,6 +2,13 @@ module QA module Page module Project class New < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/projects/new.html.haml' + def choose_test_namespace find('#s2id_project_namespace_id').click find('.select2-result-label', text: Runtime::Namespace.name).click diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb index 4028b8cccc5..a8d6f09777c 100644 --- a/qa/qa/page/project/settings/deploy_keys.rb +++ b/qa/qa/page/project/settings/deploy_keys.rb @@ -3,6 +3,13 @@ module QA module Project module Settings class DeployKeys < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/projects/deploy_keys/edit.html.haml' + def fill_key_title(title) fill_in 'deploy_key_title', with: title end diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb index 034b0d09c1c..524d87c6be9 100644 --- a/qa/qa/page/project/settings/repository.rb +++ b/qa/qa/page/project/settings/repository.rb @@ -5,6 +5,13 @@ module QA class Repository < Page::Base include Common + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/projects/settings/repository/show.html.haml' + def expand_deploy_keys(&block) expand('.qa-expand-deploy-keys') do DeployKeys.perform(&block) diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 3b2bac84f3f..90967e6cc15 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -2,6 +2,13 @@ module QA module Page module Project class Show < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#154 + # + view 'app/views/projects/show.html.haml' + def choose_repository_clone_http find('#clone-dropdown').click diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index 5a8f368f88c..c87eb5f3dfb 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -44,6 +44,8 @@ module QA end validators.each(&:validate!) + + puts 'Views / selectors validation passed!' end end end -- cgit v1.2.1 From 77c4c98aedc57dcaa9f22a612ed4697c4c7200e8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 11:14:07 +0100 Subject: Require qa:selectors sanity validation to pass --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 70342691bfc..6977ac6a5c9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -630,7 +630,6 @@ qa:selectors: variables: SETUP_DB: "false" services: [] - allow_failure: true script: - cd qa/ - bundle install -- cgit v1.2.1 From e85ed5cbcf302233450cef384974eee377f3051f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 11:15:57 +0100 Subject: Fix few typos in QA selectors validation code --- qa/qa/page/view.rb | 2 +- qa/spec/page/validator_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index ebb28d5fb12..6635e1ce039 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -9,7 +9,7 @@ module QA end def pathname - @pathname ||= Pathname.new(File.join( __dir__, '../../../', @path)) + @pathname ||= Pathname.new(File.join(__dir__, '../../../', @path)) .cleanpath.expand_path end diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb index e13fb1eae5b..02822d7d18f 100644 --- a/qa/spec/page/validator_spec.rb +++ b/qa/spec/page/validator_spec.rb @@ -4,7 +4,7 @@ describe QA::Page::Validator do described_class.new(QA::Page::Project) end - it 'returns all costants that are module children' do + it 'returns all constants that are module children' do expect(subject.constants) .to include QA::Page::Project::New, QA::Page::Project::Settings end -- cgit v1.2.1 From 817da7cf8c1caef45c37153c7eb776ed1f226625 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 10 Jan 2018 11:00:33 +0000 Subject: Added LFS to merge request files tracked by LFS Closes #41829 --- app/views/projects/diffs/_file_header.html.haml | 3 +++ changelogs/unreleased/merge-request-lfs-badge.yml | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 changelogs/unreleased/merge-request-lfs-badge.yml diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index 73c316472e3..dbeddf6689a 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -35,3 +35,6 @@ - if diff_file.mode_changed? %small #{diff_file.a_mode} → #{diff_file.b_mode} + + - if diff_file.stored_externally? && diff_file.external_storage == :lfs + %span.label.label-lfs.append-right-5 LFS diff --git a/changelogs/unreleased/merge-request-lfs-badge.yml b/changelogs/unreleased/merge-request-lfs-badge.yml new file mode 100644 index 00000000000..6c345397111 --- /dev/null +++ b/changelogs/unreleased/merge-request-lfs-badge.yml @@ -0,0 +1,5 @@ +--- +title: Added LFS badge to merge request files tracked by LFS +merge_request: +author: +type: fixed -- cgit v1.2.1 From a9ace7bf0553457a10c606a5061524e9fb708325 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 12:19:48 +0100 Subject: Expect QA selectors validation at least once We call it for a second time in EE. --- qa/spec/scenario/test/sanity/selectors_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/spec/scenario/test/sanity/selectors_spec.rb b/qa/spec/scenario/test/sanity/selectors_spec.rb index a64a60fa119..45d21d54955 100644 --- a/qa/spec/scenario/test/sanity/selectors_spec.rb +++ b/qa/spec/scenario/test/sanity/selectors_spec.rb @@ -34,7 +34,7 @@ describe QA::Scenario::Test::Sanity::Selectors do it 'triggers validation' do described_class.perform - expect(validator).to have_received(:validate!) + expect(validator).to have_received(:validate!).at_least(:once) end end end -- cgit v1.2.1 From f4a694efd8f0dd9c84ab9bbbad1ead4238e0c99f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 13:30:32 +0100 Subject: Fix using wrong `Your projects` link in QA tests --- app/views/dashboard/_projects_head.html.haml | 2 +- app/views/layouts/nav/projects_dropdown/_show.html.haml | 4 ++-- qa/qa/page/menu/main.rb | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index cc636d622a1..9038c4fbebd 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -6,7 +6,7 @@ .fade-right= icon('angle-right') %ul.nav-links.scrolling-tabs = nav_link(page: [dashboard_projects_path, root_path]) do - = link_to dashboard_projects_path, class: 'shortcuts-activity qa-your-projects-link', data: {placement: 'right'} do + = link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do Your projects = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, data: {placement: 'right'} do diff --git a/app/views/layouts/nav/projects_dropdown/_show.html.haml b/app/views/layouts/nav/projects_dropdown/_show.html.haml index 32a24c101fc..59becb043d3 100644 --- a/app/views/layouts/nav/projects_dropdown/_show.html.haml +++ b/app/views/layouts/nav/projects_dropdown/_show.html.haml @@ -1,9 +1,9 @@ - project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted? .projects-dropdown-container - .project-dropdown-sidebar + .project-dropdown-sidebar.qa-projects-dropdown-sidebar %ul = nav_link(path: 'dashboard/projects#index') do - = link_to dashboard_projects_path do + = link_to dashboard_projects_path, class: 'qa-your-projects-link' do = _('Your projects') = nav_link(path: 'projects#starred') do = link_to starred_dashboard_projects_path do diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb index 36172151969..f8978b8a5f7 100644 --- a/qa/qa/page/menu/main.rb +++ b/qa/qa/page/menu/main.rb @@ -15,7 +15,8 @@ module QA element :groups_link end - view 'app/views/dashboard/_projects_head.html.haml' do + view 'app/views/layouts/nav/projects_dropdown/_show.html.haml' do + element :projects_dropdown_sidebar element :your_projects_link end @@ -26,6 +27,9 @@ module QA def go_to_projects within_top_menu do click_element :projects_dropdown + end + + page.within('.qa-projects-dropdown-sidebar') do click_element :your_projects_link end end -- cgit v1.2.1 From b44583e9c6a00c689be398fee54b24a6dab019a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 10 Jan 2018 14:35:52 +0100 Subject: Extract GCP billing check as method --- .../projects/clusters/gcp_controller.rb | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 25608df0b9c..659bf8d8f82 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -3,6 +3,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController before_action :authorize_google_api, except: [:login] before_action :authorize_google_project_billing, only: [:new] before_action :authorize_create_cluster!, only: [:new, :create] + before_action :verify_billing, only: [:create] def login begin @@ -23,24 +24,34 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end def create + @cluster = ::Clusters::CreateService + .new(project, current_user, create_params) + .execute(token_in_session) + + if @cluster.persisted? + redirect_to project_cluster_path(project, @cluster) + else + render :new + end + end + + private + + def verify_billing case google_project_billing_status when 'true' - @cluster = ::Clusters::CreateService - .new(project, current_user, create_params) - .execute(token_in_session) - - return redirect_to project_cluster_path(project, @cluster) if @cluster.persisted? + return when 'false' - flash[:error] = _('Please enable billing for one of your projects to be able to create a cluster.') + flash[:alert] = _('Please enable billing for one of your projects to be able to create a cluster.') else - flash[:error] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') + flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') end + @cluster = ::Clusters::Cluster.new(create_params) + render :new end - private - def create_params params.require(:cluster).permit( :enabled, -- cgit v1.2.1 From ca2755619b36d11b3e25c107505aa7d99c3d8352 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 10 Jan 2018 13:43:32 +0000 Subject: Never set special MR titles for external issues --- app/services/merge_requests/build_service.rb | 8 ++------ spec/services/merge_requests/build_service_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 9781fecfb50..22b9b91a957 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -154,13 +154,9 @@ module MergeRequests end def assign_title_from_issue - return unless issue + return unless issue && issue.is_a?(Issue) - merge_request.title = - case issue - when Issue then "Resolve \"#{issue.title}\"" - when ExternalIssue then merge_request.source_branch.titleize.humanize - end + merge_request.title = "Resolve \"#{issue.title}\"" end def issue_iid diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 4b670d3530e..cb4c3e72aa0 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -171,6 +171,24 @@ describe MergeRequests::BuildService do end end end + + context 'branch starts with external issue IID followed by a hyphen' do + let(:source_branch) { '12345-fix-issue' } + + before do + allow(project).to receive(:external_issue_tracker).and_return(true) + end + + it 'uses the title of the commit as the title of the merge request' do + expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first) + end + + it 'uses the description of the commit as the description of the merge request and appends the closes text' do + commit_description = commit_1.safe_message.split(/\n+/, 2).last + + expect(merge_request.description).to eq("#{commit_description}\n\nCloses #12345") + end + end end context 'more than one commit in the diff' do @@ -244,6 +262,10 @@ describe MergeRequests::BuildService do it 'sets the title to the humanized branch title' do expect(merge_request.title).to eq('12345 fix issue') end + + it 'appends the closes text' do + expect(merge_request.description).to eq('Closes #12345') + end end end -- cgit v1.2.1 From f9cf2f99763129c4cab4ccd24b4d53886fbc8a92 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 10 Jan 2018 13:50:38 +0000 Subject: Give appropriate credit to Ben305 in the changelog --- changelogs/unreleased/36669-default-mr-title-with-external-issues.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml b/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml index 08523d01a72..6af9ac4b099 100644 --- a/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml +++ b/changelogs/unreleased/36669-default-mr-title-with-external-issues.yml @@ -1,5 +1,5 @@ --- title: Default merge request title is set correctly again when external issue tracker is activated -merge_request: -author: +merge_request: 16356 +author: Ben305 type: fixed -- cgit v1.2.1 From ee66b316419bd264254640780c00496f06278b8f Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 10 Jan 2018 12:29:55 +0000 Subject: Exclude translations from coverage report --- .babelrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.babelrc b/.babelrc index 2bae7ca9fbf..b93bef72de1 100644 --- a/.babelrc +++ b/.babelrc @@ -8,7 +8,8 @@ "plugins": [ ["istanbul", { "exclude": [ - "spec/javascripts/**/*" + "spec/javascripts/**/*", + "app/assets/javascripts/locale/**/app.js" ] }], ["transform-define", { -- cgit v1.2.1 From 29ab9e92f49ee247c4702a81a7b2c81c499a4c3b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Jan 2018 15:17:59 +0100 Subject: Add docs about QA page objects and pages DSL --- qa/qa/page/README.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 qa/qa/page/README.md diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md new file mode 100644 index 00000000000..896654c9a9c --- /dev/null +++ b/qa/qa/page/README.md @@ -0,0 +1,112 @@ +# Page objects in GitLab QA + +In GitLab QA we are using a known pattern, called _Page Objects_. + +This means that we have built an abstraction for all GitLab pages that we use +to drive GitLab QA scenarios. Whenever we do something on a page, like filling +in a form, or clicking a button, we do that only through a page object +associated with this area of GitLab. + +For example, when GitLab QA test harness signs in into GitLab, it needs to fill +in a user login and user password. In order to do that, we have a class, called +`Page::Main::Login` and `sign_in_using_credentials` methods, that is the only +piece of the code, that has knowledge about `user_login` and `user_password` +fields. + +## Why do we need that? + +We need page objects, because we need to reduce duplication and avoid problems +whenever someone changes some selectors in GitLab's source code. + +Imagine that we have a hundred specs in GitLab QA, and we need to sign into +GitLab each time, before we make assertions. Without page object one would need +to rely on volatile helpers or invoke Capybara methods directly. Imagine +invoking `fill_in :user_login` in every `*_spec.rb` file / test example. + +When someone later changes `t.text_field :login` in the view associated with +this page to `t.text_field :username` it will generate different field ID, +what would effectively break all 100 tests. + +Because we are now using `Page::Main::Login.act { sign_in_using_credentials }` +everywhere, where we want to sign into GitLab, page object is the single source +of truth, and we will need to update `fill_in :user_login` +to `fill_in :user_username` only in a one place as well. + +## What problems did we have in the past? + +We do not run QA tests for every commit, because of performance reasons, and +the time it would take to build packages and test everything. + +That is why when someone changes `t.text_field :login` to +`t.text_field :username` in new session view we won't know about this change +until our GitLab QA nightly pipeline runs, or someone triggers `package-qa` +action in their merge request. + +Obviously such a change would break all tests. We call this problem a _fragile +tests problem_. + +In order to make GitLab QA more reliable and robust, we had to solve this +problem by introducing coupling between GitLab CE / EE views and GitLab QA. + +## How did we solve fragile tests problem? + +Currently, when you add a new `Page::Base` derived class, you will also need to +define all selectors that your page objects depends on. + +Whenever your push your code to CE / EE repository, `qa:selectors` sanity test +job is going to be run as a part of a CI pipeline. + +This test is going to validate all page objects that we have implemented in +`qa/page` directory. When if fails, you will be notified about missing +or invalid views / selectors definition. + +## How to properly implement a page object? + +We have built a DSL to define coupling between a page object and GitLab views +it is actually implemented by. See an example below. + +```ruby +module Page + module Main + class Login < Page::Base + view 'app/views/devise/passwords/edit.html.haml' do + element :password_field, 'password_field :password' + element :password_confirmation, 'password_field :password_confirmation' + element :change_password_button, 'submit "Change your password"' + end + + view 'app/views/devise/sessions/_new_base.html.haml' do + element :login_field, 'text_field :login' + element :passowrd_field, 'password_field :password' + element :sign_in_button, 'submit "Sign in"' + end + + # ... + end +end +``` + +It is possible to use `element` DSL method without value, with a String value +or with a Regexp. + +```ruby +view 'app/views/my/view.html.haml' do + # Require `f.submit "Sign in"` to be present in `my/view.html.haml + element :my_button, 'f.submit "Sign in"' + + # Match every line in `my/view.html.haml` against + # `/link_to .* "My Profile"/` regexp. + element :profile_link, /link_to .* "My Profile"/ + + # Implicitly require `.qa-logout-button` CSS class to be present in the view + element :logout_button +end +``` + +## Where to ask for help? + +If you need more information, ask for help on `#qa` channel on Slack (GitLab +Team only). + +If you are not a Team Member, and you still need help to contribute, please +open an issue in GitLab QA issue tracker. -- cgit v1.2.1 From cf6258af41cee0638665560509c87bb49135081d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 10 Jan 2018 15:23:22 +0100 Subject: Fix billing checking --- app/controllers/projects/clusters/gcp_controller.rb | 2 +- app/services/check_gcp_project_billing_service.rb | 5 ++++- app/workers/check_gcp_project_billing_worker.rb | 4 ++-- lib/google_api/cloud_platform/client.rb | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 659bf8d8f82..e0729c60670 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -1,7 +1,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController before_action :authorize_read_cluster! before_action :authorize_google_api, except: [:login] - before_action :authorize_google_project_billing, only: [:new] + before_action :authorize_google_project_billing, only: [:new, :create] before_action :authorize_create_cluster!, only: [:new, :create] before_action :verify_billing, only: [:create] diff --git a/app/services/check_gcp_project_billing_service.rb b/app/services/check_gcp_project_billing_service.rb index 854adf2177d..ea82b61b279 100644 --- a/app/services/check_gcp_project_billing_service.rb +++ b/app/services/check_gcp_project_billing_service.rb @@ -2,7 +2,10 @@ class CheckGcpProjectBillingService def execute(token) client = GoogleApi::CloudPlatform::Client.new(token, nil) client.projects_list.select do |project| - client.projects_get_billing_info(project.name).billingEnabled + begin + client.projects_get_billing_info(project.project_id).billing_enabled + rescue + end end end end diff --git a/app/workers/check_gcp_project_billing_worker.rb b/app/workers/check_gcp_project_billing_worker.rb index 557af14ee57..6a0d8ab263f 100644 --- a/app/workers/check_gcp_project_billing_worker.rb +++ b/app/workers/check_gcp_project_billing_worker.rb @@ -23,13 +23,13 @@ class CheckGcpProjectBillingWorker end def self.redis_shared_state_key_for(token) - "gitlab:gcp:#{token.hash}:billing_enabled" + "gitlab:gcp:#{Digest::SHA1.hexdigest(token)}:billing_enabled" end def perform(token_key) return unless token_key - token = self.get_session_token(token_key) + token = self.class.get_session_token(token_key) return unless token return unless try_obtain_lease_for(token) diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index f05d001fd02..a9bea218692 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -51,11 +51,11 @@ module GoogleApi end end - def projects_get_billing_info(project_name) + def projects_get_billing_info(project_id) service = Google::Apis::CloudbillingV1::CloudbillingService.new service.authorization = access_token - service.get_project_billing_info("projects/#{project_name}") + service.get_project_billing_info("projects/#{project_id}") end def projects_zones_clusters_get(project_id, zone, cluster_id) -- cgit v1.2.1 From 4a81c3ae6ae9b4137292c716c004ee83e6d0fdf4 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sat, 6 Jan 2018 11:54:59 +0900 Subject: Make rich blob viewer wider for PC --- app/assets/stylesheets/framework/files.scss | 7 +------ app/assets/stylesheets/pages/repo.scss | 6 ------ changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml | 5 +++++ 3 files changed, 6 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 1588036aeae..af400da94d6 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -21,11 +21,6 @@ max-width: $limited-layout-width-sm; margin-left: auto; margin-right: auto; - - @media (min-width: $screen-md-min) { - padding-top: 64px; - padding-bottom: 64px; - } } } @@ -128,7 +123,7 @@ } &.wiki { - padding: 30px $gl-padding; + padding: $gl-padding; } &.blob-no-preview { diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 6cb32408a48..acbd9936706 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -16,12 +16,6 @@ display: inline-block; } -@media (min-width: $screen-md-min) { - .blob-viewer[data-type="rich"] { - margin: 20px; - } -} - .ide-view { display: flex; height: calc(100vh - #{$header-height}); diff --git a/changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml b/changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml new file mode 100644 index 00000000000..51285e5476f --- /dev/null +++ b/changelogs/unreleased/41709-rich-blob-viewer-margins-for-pc.yml @@ -0,0 +1,5 @@ +--- +title: Make rich blob viewer wider for PC +merge_request: 16262 +author: Takuya Noguchi +type: fixed -- cgit v1.2.1 From 0cdd56e65816eaf7dd1ff78b0a3137152d366034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 10 Jan 2018 15:44:15 +0100 Subject: Fix link to billing --- app/controllers/projects/clusters/gcp_controller.rb | 3 ++- app/workers/check_gcp_project_billing_worker.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index e0729c60670..44ccfaf2402 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -42,7 +42,8 @@ class Projects::Clusters::GcpController < Projects::ApplicationController when 'true' return when 'false' - flash[:alert] = _('Please enable billing for one of your projects to be able to create a cluster.') + flash[:alert] = _('Please enable billing for one of your projects to be able to create a cluster. Please try again.').html_safe % + { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" } else flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') end diff --git a/app/workers/check_gcp_project_billing_worker.rb b/app/workers/check_gcp_project_billing_worker.rb index 6a0d8ab263f..5466ccdda59 100644 --- a/app/workers/check_gcp_project_billing_worker.rb +++ b/app/workers/check_gcp_project_billing_worker.rb @@ -4,7 +4,7 @@ class CheckGcpProjectBillingWorker include ApplicationWorker include ClusterQueue - LEASE_TIMEOUT = 15.seconds.to_i + LEASE_TIMEOUT = 3.seconds.to_i SESSION_KEY_TIMEOUT = 5.minutes BILLING_TIMEOUT = 1.hour -- cgit v1.2.1 From cb8ab6b524114c89cff3cbfb5cdfe23cfe72f15b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 10 Jan 2018 11:45:11 +0000 Subject: Explicitly enable webmock in spinach env --- features/support/env.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/support/env.rb b/features/support/env.rb index 91a92314959..7f5b4c1c11b 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -10,14 +10,14 @@ if ENV['CI'] Knapsack::Adapters::SpinachAdapter.bind end -%w(select2_helper test_env repo_helpers wait_for_requests sidekiq project_forks_helper).each do |f| +WebMock.enable! + +%w(select2_helper test_env repo_helpers wait_for_requests sidekiq project_forks_helper webmock).each do |f| require Rails.root.join('spec', 'support', f) end Dir["#{Rails.root}/features/steps/shared/*.rb"].each { |file| require file } -WebMock.allow_net_connect! - Spinach.hooks.before_run do include RSpec::Mocks::ExampleMethods RSpec::Mocks.setup -- cgit v1.2.1 From 7dd7ccdaca8e00a9f7983d80edb6cb721ee94695 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 10 Jan 2018 15:28:33 +0000 Subject: Removed CHANGELOG entry [ci skip] --- changelogs/unreleased/merge-request-lfs-badge.yml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 changelogs/unreleased/merge-request-lfs-badge.yml diff --git a/changelogs/unreleased/merge-request-lfs-badge.yml b/changelogs/unreleased/merge-request-lfs-badge.yml deleted file mode 100644 index 6c345397111..00000000000 --- a/changelogs/unreleased/merge-request-lfs-badge.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added LFS badge to merge request files tracked by LFS -merge_request: -author: -type: fixed -- cgit v1.2.1 From e331ca13e5b29da6b5df7955545d0936809486c2 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 10 Jan 2018 13:24:00 +0000 Subject: Add tooltip missing to clipboard component Adds tests --- .../vue_shared/components/clipboard_button.vue | 7 +++++- .../vue_shared/components/clipboard_button_spec.js | 25 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 spec/javascripts/vue_shared/components/clipboard_button_spec.js diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index e18852af6e9..577709dfc1f 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -1,10 +1,14 @@ -- cgit v1.2.1 From a783158bbf52b3d115f9d102499719cc365de81e Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Wed, 10 Jan 2018 12:55:52 +0530 Subject: Update tests for modal changes --- .../groups/components/item_actions_spec.js | 40 ++++------------------ 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js index 6d6fb410859..acccbe639c4 100644 --- a/spec/javascripts/groups/components/item_actions_spec.js +++ b/spec/javascripts/groups/components/item_actions_spec.js @@ -26,32 +26,12 @@ describe('ItemActionsComponent', () => { vm.$destroy(); }); - describe('computed', () => { - describe('leaveConfirmationMessage', () => { - it('should return appropriate string for leave group confirmation', () => { - expect(vm.leaveConfirmationMessage).toBe('Are you sure you want to leave the "platform / hardware" group?'); - }); - }); - }); - describe('methods', () => { describe('onLeaveGroup', () => { - it('should change `modalStatus` prop to `true` which shows confirmation dialog', () => { - expect(vm.modalStatus).toBeFalsy(); - vm.onLeaveGroup(); - expect(vm.modalStatus).toBeTruthy(); - }); - }); - - describe('leaveGroup', () => { - it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { + it('emits `showLeaveGroupModal` event with `group` and `parentGroup` props', () => { spyOn(eventHub, '$emit'); - vm.modalStatus = true; - - vm.leaveGroup(); - - expect(vm.modalStatus).toBeFalsy(); - expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); + vm.onLeaveGroup(); + expect(eventHub.$emit).toHaveBeenCalledWith('showLeaveGroupModal', vm.group, vm.parentGroup); }); }); }); @@ -72,7 +52,8 @@ describe('ItemActionsComponent', () => { expect(editBtn.getAttribute('href')).toBe(group.editPath); expect(editBtn.getAttribute('aria-label')).toBe('Edit group'); expect(editBtn.dataset.originalTitle).toBe('Edit group'); - expect(editBtn.querySelector('i.fa.fa-cogs')).toBeDefined(); + expect(editBtn.querySelectorAll('svg use').length).not.toBe(0); + expect(editBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#settings'); newVm.$destroy(); }); @@ -88,17 +69,10 @@ describe('ItemActionsComponent', () => { expect(leaveBtn.getAttribute('href')).toBe(group.leavePath); expect(leaveBtn.getAttribute('aria-label')).toBe('Leave this group'); expect(leaveBtn.dataset.originalTitle).toBe('Leave this group'); - expect(leaveBtn.querySelector('i.fa.fa-sign-out')).toBeDefined(); + expect(leaveBtn.querySelectorAll('svg use').length).not.toBe(0); + expect(leaveBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#leave'); newVm.$destroy(); }); - - it('should show modal dialog when `modalStatus` is set to `true`', () => { - vm.modalStatus = true; - const modalDialogEl = vm.$el.querySelector('.modal'); - expect(modalDialogEl).toBeDefined(); - expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); - expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave'); - }); }); }); -- cgit v1.2.1 From 3545ceb02c08b71422b51209a2cca21155236cfc Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 11 Jan 2018 08:04:50 +0000 Subject: apply documentation changes from EE to CE --- doc/api/snippets.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/api/snippets.md b/doc/api/snippets.md index fdafbfb5b9e..e57143e4215 100644 --- a/doc/api/snippets.md +++ b/doc/api/snippets.md @@ -84,7 +84,11 @@ Parameters: ``` bash -curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets +curl --request POST \ + --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \ + --header 'Content-Type: application/json' \ + --header "PRIVATE-TOKEN: valid_api_token" \ + https://gitlab.example.com/api/v4/snippets ``` Example response: @@ -131,7 +135,11 @@ Parameters: ``` bash -curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data '{"title": "foo", "content": "bar"}' https://gitlab.example.com/api/v4/snippets/1 +curl --request PUT \ + --data '{"title": "foo", "content": "bar"}' \ + --header 'Content-Type: application/json' \ + --header "PRIVATE-TOKEN: valid_api_token" \ + https://gitlab.example.com/api/v4/snippets/1 ``` Example response: -- cgit v1.2.1 From 407d56384460806ed2e2c88657ce35750527aae5 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Thu, 11 Jan 2018 08:23:44 +0000 Subject: Fix up Web IDE user preference copy and buttons --- app/assets/images/multi-editor-on.png | Bin 5464 -> 3971 bytes app/helpers/blob_helper.rb | 2 +- app/views/layouts/header/_default.html.haml | 2 -- app/views/profiles/preferences/show.html.haml | 4 ++-- ...ix-up-web-ide-user-preference-copy-and-buttons.yml | 5 +++++ spec/features/projects/tree/create_directory_spec.rb | 2 +- spec/features/projects/tree/create_file_spec.rb | 2 +- spec/features/projects/tree/upload_file_spec.rb | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml diff --git a/app/assets/images/multi-editor-on.png b/app/assets/images/multi-editor-on.png index 2bcd29abf13..d51b68da985 100644 Binary files a/app/assets/images/multi-editor-on.png and b/app/assets/images/multi-editor-on.png differ diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index f9dcb32f7c4..5e3b2e5581c 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -46,7 +46,7 @@ module BlobHelper end def ide_edit_text - "#{_('Multi Edit')} #{_('Beta')}".html_safe + "#{_('Web IDE')}" end def ide_blob_link(project = @project, ref = @ref, path = @path, options = {}) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 39eb71c2bac..99e7f3b568d 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -56,8 +56,6 @@ = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } %li = link_to "Settings", profile_path - %li - = link_to "Turn on multi edit", profile_preferences_path - if current_user %li = link_to "Help", help_path diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 65328791ce5..aeae7455a1c 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -5,8 +5,8 @@ = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| .col-lg-4 %h4.prepend-top-0 - GitLab multi file editor - %p Unlock an additional editing experience which makes it possible to edit and commit multiple files + Web IDE (Beta) + %p Enable the new web IDE on this device to make it possible to open and edit multiple files with a single commit .col-lg-8.multi-file-editor-options = label_tag do .preview.append-bottom-10= image_tag "multi-editor-off.png" diff --git a/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml b/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml new file mode 100644 index 00000000000..fe87cd5cadb --- /dev/null +++ b/changelogs/unreleased/41789-fix-up-web-ide-user-preference-copy-and-buttons.yml @@ -0,0 +1,5 @@ +--- +title: Fix web ide user preferences copy and buttons +merge_request: 41789 +author: +type: other diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb index 3f6d16c8acf..0c67196f53e 100644 --- a/spec/features/projects/tree/create_directory_spec.rb +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -14,7 +14,7 @@ feature 'Multi-file editor new directory', :js do wait_for_requests - click_link('Multi Edit') + click_link('Web IDE') wait_for_requests end diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb index ba71eef07f4..85f7318c05d 100644 --- a/spec/features/projects/tree/create_file_spec.rb +++ b/spec/features/projects/tree/create_file_spec.rb @@ -14,7 +14,7 @@ feature 'Multi-file editor new file', :js do wait_for_requests - click_link('Multi Edit') + click_link('Web IDE') wait_for_requests end diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb index 9fbb1dbd0e8..f81e8677e92 100644 --- a/spec/features/projects/tree/upload_file_spec.rb +++ b/spec/features/projects/tree/upload_file_spec.rb @@ -16,7 +16,7 @@ feature 'Multi-file editor upload file', :js do wait_for_requests - click_link('Multi Edit') + click_link('Web IDE') wait_for_requests end -- cgit v1.2.1 From a607f343c5c28511e92e058a9556b89f897ae8c2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 9 Jan 2018 12:10:53 +0000 Subject: Admin dispatcher JS imports --- app/assets/javascripts/abuse_reports.js | 36 ------------- app/assets/javascripts/admin.js | 59 ---------------------- app/assets/javascripts/broadcast_message.js | 28 ---------- app/assets/javascripts/dispatcher.js | 44 ++++++++-------- .../pages/admin/abuse_reports/abuse_reports.js | 36 +++++++++++++ .../javascripts/pages/admin/abuse_reports/index.js | 3 ++ app/assets/javascripts/pages/admin/admin.js | 59 ++++++++++++++++++++++ .../admin/broadcast_messages/broadcast_message.js | 28 ++++++++++ .../pages/admin/broadcast_messages/index.js | 3 ++ .../javascripts/pages/admin/cohorts/index.js | 3 ++ .../javascripts/pages/admin/cohorts/usage_ping.js | 12 +++++ .../javascripts/pages/admin/groups/edit/index.js | 3 ++ .../javascripts/pages/admin/groups/new/index.js | 9 ++++ .../javascripts/pages/admin/groups/show/index.js | 3 ++ .../pages/admin/impersonation_tokens/index.js | 3 ++ app/assets/javascripts/pages/admin/index.js | 3 ++ .../javascripts/pages/admin/labels/edit/index.js | 3 ++ .../javascripts/pages/admin/labels/new/index.js | 3 ++ .../javascripts/pages/admin/projects/index.js | 9 ++++ app/assets/javascripts/usage_ping.js | 12 ----- 20 files changed, 204 insertions(+), 155 deletions(-) delete mode 100644 app/assets/javascripts/abuse_reports.js delete mode 100644 app/assets/javascripts/admin.js delete mode 100644 app/assets/javascripts/broadcast_message.js create mode 100644 app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js create mode 100644 app/assets/javascripts/pages/admin/abuse_reports/index.js create mode 100644 app/assets/javascripts/pages/admin/admin.js create mode 100644 app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js create mode 100644 app/assets/javascripts/pages/admin/broadcast_messages/index.js create mode 100644 app/assets/javascripts/pages/admin/cohorts/index.js create mode 100644 app/assets/javascripts/pages/admin/cohorts/usage_ping.js create mode 100644 app/assets/javascripts/pages/admin/groups/edit/index.js create mode 100644 app/assets/javascripts/pages/admin/groups/new/index.js create mode 100644 app/assets/javascripts/pages/admin/groups/show/index.js create mode 100644 app/assets/javascripts/pages/admin/impersonation_tokens/index.js create mode 100644 app/assets/javascripts/pages/admin/index.js create mode 100644 app/assets/javascripts/pages/admin/labels/edit/index.js create mode 100644 app/assets/javascripts/pages/admin/labels/new/index.js create mode 100644 app/assets/javascripts/pages/admin/projects/index.js delete mode 100644 app/assets/javascripts/usage_ping.js diff --git a/app/assets/javascripts/abuse_reports.js b/app/assets/javascripts/abuse_reports.js deleted file mode 100644 index d2d3a257c0d..00000000000 --- a/app/assets/javascripts/abuse_reports.js +++ /dev/null @@ -1,36 +0,0 @@ -import { truncate } from './lib/utils/text_utility'; - -const MAX_MESSAGE_LENGTH = 500; -const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; - -export default class AbuseReports { - constructor() { - $(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage); - $(document) - .off('click', MESSAGE_CELL_SELECTOR) - .on('click', MESSAGE_CELL_SELECTOR, this.toggleMessageTruncation); - } - - truncateLongMessage() { - const $messageCellElement = $(this); - const reportMessage = $messageCellElement.text(); - if (reportMessage.length > MAX_MESSAGE_LENGTH) { - $messageCellElement.data('original-message', reportMessage); - $messageCellElement.data('message-truncated', 'true'); - $messageCellElement.text(truncate(reportMessage, MAX_MESSAGE_LENGTH)); - } - } - - toggleMessageTruncation() { - const $messageCellElement = $(this); - const originalMessage = $messageCellElement.data('original-message'); - if (!originalMessage) return; - if ($messageCellElement.data('message-truncated') === 'true') { - $messageCellElement.data('message-truncated', 'false'); - $messageCellElement.text(originalMessage); - } else { - $messageCellElement.data('message-truncated', 'true'); - $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`); - } - } -} diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js deleted file mode 100644 index c1f7fa2aced..00000000000 --- a/app/assets/javascripts/admin.js +++ /dev/null @@ -1,59 +0,0 @@ -import { refreshCurrentPage } from './lib/utils/url_utility'; - -function showBlacklistType() { - if ($('input[name="blacklist_type"]:checked').val() === 'file') { - $('.blacklist-file').show(); - $('.blacklist-raw').hide(); - } else { - $('.blacklist-file').hide(); - $('.blacklist-raw').show(); - } -} - -export default function adminInit() { - const modal = $('.change-owner-holder'); - - $('input#user_force_random_password').on('change', function randomPasswordClick() { - const $elems = $('#user_password, #user_password_confirmation'); - if ($(this).attr('checked')) { - $elems.val('').attr('disabled', true); - } else { - $elems.removeAttr('disabled'); - } - }); - - $('body').on('click', '.js-toggle-colors-link', (e) => { - e.preventDefault(); - $('.js-toggle-colors-container').toggle(); - }); - - $('.log-tabs a').on('click', function logTabsClick(e) { - e.preventDefault(); - $(this).tab('show'); - }); - - $('.log-bottom').on('click', (e) => { - e.preventDefault(); - const $visibleLog = $('.file-content:visible'); - $visibleLog.animate({ - scrollTop: $visibleLog.find('ol').height(), - }, 'fast'); - }); - - $('.change-owner-link').on('click', function changeOwnerLinkClick(e) { - e.preventDefault(); - $(this).hide(); - modal.show(); - }); - - $('.change-owner-cancel-link').on('click', (e) => { - e.preventDefault(); - modal.hide(); - $('.change-owner-link').show(); - }); - - $('li.project_member, li.group_member').on('ajax:success', refreshCurrentPage); - - $("input[name='blacklist_type']").on('click', showBlacklistType); - showBlacklistType(); -} diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js deleted file mode 100644 index ff88083a4b4..00000000000 --- a/app/assets/javascripts/broadcast_message.js +++ /dev/null @@ -1,28 +0,0 @@ -export default function initBroadcastMessagesForm() { - $('input#broadcast_message_color').on('input', function onMessageColorInput() { - const previewColor = $(this).val(); - $('div.broadcast-message-preview').css('background-color', previewColor); - }); - - $('input#broadcast_message_font').on('input', function onMessageFontInput() { - const previewColor = $(this).val(); - $('div.broadcast-message-preview').css('color', previewColor); - }); - - const previewPath = $('textarea#broadcast_message_message').data('preview-path'); - - $('textarea#broadcast_message_message').on('input', _.debounce(function onMessageInput() { - const message = $(this).val(); - if (message === '') { - $('.js-broadcast-message-preview').text('Your message here'); - } else { - $.ajax({ - url: previewPath, - type: 'POST', - data: { - broadcast_message: { message }, - }, - }); - } - }, 250)); -} diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 9d9605045fb..d0073a1a403 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -13,8 +13,6 @@ import groupAvatar from './group_avatar'; import GroupLabelSubscription from './group_label_subscription'; import LineHighlighter from './line_highlighter'; import groupsSelect from './groups_select'; -import initAdmin from './admin'; -import NamespaceSelect from './namespace_select'; import NewCommitForm from './new_commit_form'; import Project from './project'; import projectAvatar from './project_avatar'; @@ -51,14 +49,12 @@ import GfmAutoComplete from './gfm_auto_complete'; import ShortcutsBlob from './shortcuts_blob'; import Star from './star'; import TreeView from './tree'; -import UsagePing from './usage_ping'; import VersionCheckImage from './version_check_image'; import Wikis from './wikis'; import ZenMode from './zen_mode'; import initSettingsPanels from './settings_panels'; import initExperimentalFlags from './experimental_flags'; import PerformanceBar from './performance_bar'; -import initBroadcastMessagesForm from './broadcast_message'; import initNotes from './init_notes'; import initLegacyFilters from './init_legacy_filters'; import initIssuableSidebar from './init_issuable_sidebar'; @@ -66,7 +62,6 @@ import initProjectVisibilitySelector from './project_visibility'; import GpgBadges from './gpg_badges'; import initChangesDropdown from './init_changes_dropdown'; import NewGroupChild from './groups/new_group_child'; -import AbuseReports from './abuse_reports'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import AjaxLoadingSpinner from './ajax_loading_spinner'; import GlFieldErrors from './gl_field_errors'; @@ -229,9 +224,6 @@ import Activities from './activities'; .then(callDefault) .catch(fail); break; - case 'admin:projects:index': - new ProjectsList(); - break; case 'explore:groups:index': import('./pages/explore/groups') .then(callDefault) @@ -441,15 +433,19 @@ import Activities from './activities'; new UsersSelect(); break; case 'groups:new': - case 'admin:groups:new': case 'groups:create': - case 'admin:groups:create': BindInOut.initAll(); new Group(); groupAvatar(); break; - case 'groups:edit': + case 'admin:groups:create': + case 'admin:groups:new': + import('./pages/admin/groups/new').then(m => m.default()).catch(fail); + break; case 'admin:groups:edit': + import('./pages/admin/groups/edit').then(m => m.default()).catch(fail); + break; + case 'groups:edit': groupAvatar(); break; case 'projects:tree:show': @@ -565,8 +561,10 @@ import Activities from './activities'; case 'import:fogbugz:new_user_map': import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail); break; - case 'profiles:personal_access_tokens:index': case 'admin:impersonation_tokens:index': + import('./pages/admin/impersonation_tokens').then(m => m.default()).catch(fail); + break; + case 'profiles:personal_access_tokens:index': new DueDateSelectors(); break; case 'projects:clusters:show': @@ -601,29 +599,35 @@ import Activities from './activities'; // needed in rspec gl.u2fAuthenticate = u2fAuthenticate; case 'admin': - initAdmin(); + import('./pages/admin').then(m => m.default()).catch(fail); switch (path[1]) { case 'broadcast_messages': - initBroadcastMessagesForm(); + import('./pages/admin/broadcast_messages').then(m => m.default()).catch(fail); break; case 'cohorts': - new UsagePing(); + import('./pages/admin/cohorts').then(m => m.default()).catch(fail); break; case 'groups': - new UsersSelect(); + switch (path[2]) { + case 'show': + import('./pages/admin/groups/show').then(m => m.default()).catch(fail); + break; + } break; case 'projects': - document.querySelectorAll('.js-namespace-select') - .forEach(dropdown => new NamespaceSelect({ dropdown })); + import('./pages/admin/projects').then(m => m.default()).catch(fail); break; case 'labels': switch (path[2]) { case 'new': + import('./pages/admin/labels/new').then(m => m.default()).catch(fail); + break; case 'edit': - new Labels(); + import('./pages/admin/labels/edit').then(m => m.default()).catch(fail); + break; } case 'abuse_reports': - new AbuseReports(); + import('./pages/admin/abuse_reports').then(m => m.default()).catch(fail); break; } break; diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js new file mode 100644 index 00000000000..d87e6304a24 --- /dev/null +++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js @@ -0,0 +1,36 @@ +import { truncate } from '../../../lib/utils/text_utility'; + +const MAX_MESSAGE_LENGTH = 500; +const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; + +export default class AbuseReports { + constructor() { + $(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage); + $(document) + .off('click', MESSAGE_CELL_SELECTOR) + .on('click', MESSAGE_CELL_SELECTOR, this.toggleMessageTruncation); + } + + truncateLongMessage() { + const $messageCellElement = $(this); + const reportMessage = $messageCellElement.text(); + if (reportMessage.length > MAX_MESSAGE_LENGTH) { + $messageCellElement.data('original-message', reportMessage); + $messageCellElement.data('message-truncated', 'true'); + $messageCellElement.text(truncate(reportMessage, MAX_MESSAGE_LENGTH)); + } + } + + toggleMessageTruncation() { + const $messageCellElement = $(this); + const originalMessage = $messageCellElement.data('original-message'); + if (!originalMessage) return; + if ($messageCellElement.data('message-truncated') === 'true') { + $messageCellElement.data('message-truncated', 'false'); + $messageCellElement.text(originalMessage); + } else { + $messageCellElement.data('message-truncated', 'true'); + $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`); + } + } +} diff --git a/app/assets/javascripts/pages/admin/abuse_reports/index.js b/app/assets/javascripts/pages/admin/abuse_reports/index.js new file mode 100644 index 00000000000..c0b6e8d4095 --- /dev/null +++ b/app/assets/javascripts/pages/admin/abuse_reports/index.js @@ -0,0 +1,3 @@ +import AbuseReports from './abuse_reports'; + +export default () => new AbuseReports(); diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js new file mode 100644 index 00000000000..135c15c346b --- /dev/null +++ b/app/assets/javascripts/pages/admin/admin.js @@ -0,0 +1,59 @@ +import { refreshCurrentPage } from '../../lib/utils/url_utility'; + +function showBlacklistType() { + if ($('input[name="blacklist_type"]:checked').val() === 'file') { + $('.blacklist-file').show(); + $('.blacklist-raw').hide(); + } else { + $('.blacklist-file').hide(); + $('.blacklist-raw').show(); + } +} + +export default function adminInit() { + const modal = $('.change-owner-holder'); + + $('input#user_force_random_password').on('change', function randomPasswordClick() { + const $elems = $('#user_password, #user_password_confirmation'); + if ($(this).attr('checked')) { + $elems.val('').attr('disabled', true); + } else { + $elems.removeAttr('disabled'); + } + }); + + $('body').on('click', '.js-toggle-colors-link', (e) => { + e.preventDefault(); + $('.js-toggle-colors-container').toggle(); + }); + + $('.log-tabs a').on('click', function logTabsClick(e) { + e.preventDefault(); + $(this).tab('show'); + }); + + $('.log-bottom').on('click', (e) => { + e.preventDefault(); + const $visibleLog = $('.file-content:visible'); + $visibleLog.animate({ + scrollTop: $visibleLog.find('ol').height(), + }, 'fast'); + }); + + $('.change-owner-link').on('click', function changeOwnerLinkClick(e) { + e.preventDefault(); + $(this).hide(); + modal.show(); + }); + + $('.change-owner-cancel-link').on('click', (e) => { + e.preventDefault(); + modal.hide(); + $('.change-owner-link').show(); + }); + + $('li.project_member, li.group_member').on('ajax:success', refreshCurrentPage); + + $("input[name='blacklist_type']").on('click', showBlacklistType); + showBlacklistType(); +} diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js new file mode 100644 index 00000000000..ff88083a4b4 --- /dev/null +++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js @@ -0,0 +1,28 @@ +export default function initBroadcastMessagesForm() { + $('input#broadcast_message_color').on('input', function onMessageColorInput() { + const previewColor = $(this).val(); + $('div.broadcast-message-preview').css('background-color', previewColor); + }); + + $('input#broadcast_message_font').on('input', function onMessageFontInput() { + const previewColor = $(this).val(); + $('div.broadcast-message-preview').css('color', previewColor); + }); + + const previewPath = $('textarea#broadcast_message_message').data('preview-path'); + + $('textarea#broadcast_message_message').on('input', _.debounce(function onMessageInput() { + const message = $(this).val(); + if (message === '') { + $('.js-broadcast-message-preview').text('Your message here'); + } else { + $.ajax({ + url: previewPath, + type: 'POST', + data: { + broadcast_message: { message }, + }, + }); + } + }, 250)); +} diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/index.js b/app/assets/javascripts/pages/admin/broadcast_messages/index.js new file mode 100644 index 00000000000..b548c48282a --- /dev/null +++ b/app/assets/javascripts/pages/admin/broadcast_messages/index.js @@ -0,0 +1,3 @@ +import initBroadcastMessagesForm from './broadcast_message'; + +export default () => initBroadcastMessagesForm(); diff --git a/app/assets/javascripts/pages/admin/cohorts/index.js b/app/assets/javascripts/pages/admin/cohorts/index.js new file mode 100644 index 00000000000..42ef9d38ef7 --- /dev/null +++ b/app/assets/javascripts/pages/admin/cohorts/index.js @@ -0,0 +1,3 @@ +import initUsagePing from './usage_ping'; + +export default () => initUsagePing(); diff --git a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js new file mode 100644 index 00000000000..2389056bd02 --- /dev/null +++ b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js @@ -0,0 +1,12 @@ +export default function UsagePing() { + const usageDataUrl = $('.usage-data').data('endpoint'); + + $.ajax({ + type: 'GET', + url: usageDataUrl, + dataType: 'html', + success(html) { + $('.usage-data').html(html); + }, + }); +} diff --git a/app/assets/javascripts/pages/admin/groups/edit/index.js b/app/assets/javascripts/pages/admin/groups/edit/index.js new file mode 100644 index 00000000000..ff9ef8d2449 --- /dev/null +++ b/app/assets/javascripts/pages/admin/groups/edit/index.js @@ -0,0 +1,3 @@ +import groupAvatar from '../../../../group_avatar'; + +export default () => groupAvatar(); diff --git a/app/assets/javascripts/pages/admin/groups/new/index.js b/app/assets/javascripts/pages/admin/groups/new/index.js new file mode 100644 index 00000000000..fb5c46e4729 --- /dev/null +++ b/app/assets/javascripts/pages/admin/groups/new/index.js @@ -0,0 +1,9 @@ +import BindInOut from '../../../../behaviors/bind_in_out'; +import Group from '../../../../group'; +import groupAvatar from '../../../../group_avatar'; + +export default () => { + BindInOut.initAll(); + new Group(); // eslint-disable-line no-new + groupAvatar(); +}; diff --git a/app/assets/javascripts/pages/admin/groups/show/index.js b/app/assets/javascripts/pages/admin/groups/show/index.js new file mode 100644 index 00000000000..5defea104d4 --- /dev/null +++ b/app/assets/javascripts/pages/admin/groups/show/index.js @@ -0,0 +1,3 @@ +import UsersSelect from '../../../../users_select'; + +export default () => new UsersSelect(); diff --git a/app/assets/javascripts/pages/admin/impersonation_tokens/index.js b/app/assets/javascripts/pages/admin/impersonation_tokens/index.js new file mode 100644 index 00000000000..030328a1363 --- /dev/null +++ b/app/assets/javascripts/pages/admin/impersonation_tokens/index.js @@ -0,0 +1,3 @@ +import DueDateSelectors from '../../../due_date_select'; + +export default () => new DueDateSelectors(); diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js new file mode 100644 index 00000000000..8b843037d85 --- /dev/null +++ b/app/assets/javascripts/pages/admin/index.js @@ -0,0 +1,3 @@ +import initAdmin from './admin'; + +export default () => initAdmin(); diff --git a/app/assets/javascripts/pages/admin/labels/edit/index.js b/app/assets/javascripts/pages/admin/labels/edit/index.js new file mode 100644 index 00000000000..d7ec6e47f67 --- /dev/null +++ b/app/assets/javascripts/pages/admin/labels/edit/index.js @@ -0,0 +1,3 @@ +import Labels from '../../../../labels'; + +export default () => new Labels(); diff --git a/app/assets/javascripts/pages/admin/labels/new/index.js b/app/assets/javascripts/pages/admin/labels/new/index.js new file mode 100644 index 00000000000..d7ec6e47f67 --- /dev/null +++ b/app/assets/javascripts/pages/admin/labels/new/index.js @@ -0,0 +1,3 @@ +import Labels from '../../../../labels'; + +export default () => new Labels(); diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js new file mode 100644 index 00000000000..71e0ddcd7b6 --- /dev/null +++ b/app/assets/javascripts/pages/admin/projects/index.js @@ -0,0 +1,9 @@ +import ProjectsList from '../../../projects_list'; +import NamespaceSelect from '../../../namespace_select'; + +export default () => { + new ProjectsList(); // eslint-disable-line no-new + + document.querySelectorAll('.js-namespace-select') + .forEach(dropdown => new NamespaceSelect({ dropdown })); +}; diff --git a/app/assets/javascripts/usage_ping.js b/app/assets/javascripts/usage_ping.js deleted file mode 100644 index 2389056bd02..00000000000 --- a/app/assets/javascripts/usage_ping.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function UsagePing() { - const usageDataUrl = $('.usage-data').data('endpoint'); - - $.ajax({ - type: 'GET', - url: usageDataUrl, - dataType: 'html', - success(html) { - $('.usage-data').html(html); - }, - }); -} -- cgit v1.2.1 From 3b9a1f36ee4af34fdcb82a29537d8ef20cb67270 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 9 Jan 2018 12:58:05 +0000 Subject: fixed abuse_reports spec --- spec/javascripts/abuse_reports_spec.js | 41 ---------------------- .../admin/abuse_reports/abuse_reports_spec.js | 41 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 41 deletions(-) delete mode 100644 spec/javascripts/abuse_reports_spec.js create mode 100644 spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/abuse_reports_spec.js deleted file mode 100644 index 7f6b5873011..00000000000 --- a/spec/javascripts/abuse_reports_spec.js +++ /dev/null @@ -1,41 +0,0 @@ -import '~/lib/utils/text_utility'; -import AbuseReports from '~/abuse_reports'; - -describe('Abuse Reports', () => { - const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; - const MAX_MESSAGE_LENGTH = 500; - - let $messages; - - const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); - const findMessage = searchText => $messages.filter( - (index, element) => element.innerText.indexOf(searchText) > -1, - ).first(); - - preloadFixtures(FIXTURE); - - beforeEach(function () { - loadFixtures(FIXTURE); - this.abuseReports = new AbuseReports(); - $messages = $('.abuse-reports .message'); - }); - - it('should truncate long messages', () => { - const $longMessage = findMessage('LONG MESSAGE'); - expect($longMessage.data('original-message')).toEqual(jasmine.anything()); - assertMaxLength($longMessage); - }); - - it('should not truncate short messages', () => { - const $shortMessage = findMessage('SHORT MESSAGE'); - expect($shortMessage.data('original-message')).not.toEqual(jasmine.anything()); - }); - - it('should allow clicking a truncated message to expand and collapse the full message', () => { - const $longMessage = findMessage('LONG MESSAGE'); - $longMessage.click(); - expect($longMessage.data('original-message').length).toEqual($longMessage.text().length); - $longMessage.click(); - assertMaxLength($longMessage); - }); -}); diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js new file mode 100644 index 00000000000..d2386077aa6 --- /dev/null +++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js @@ -0,0 +1,41 @@ +import '~/lib/utils/text_utility'; +import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; + +describe('Abuse Reports', () => { + const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; + const MAX_MESSAGE_LENGTH = 500; + + let $messages; + + const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); + const findMessage = searchText => $messages.filter( + (index, element) => element.innerText.indexOf(searchText) > -1, + ).first(); + + preloadFixtures(FIXTURE); + + beforeEach(function () { + loadFixtures(FIXTURE); + this.abuseReports = new AbuseReports(); + $messages = $('.abuse-reports .message'); + }); + + it('should truncate long messages', () => { + const $longMessage = findMessage('LONG MESSAGE'); + expect($longMessage.data('original-message')).toEqual(jasmine.anything()); + assertMaxLength($longMessage); + }); + + it('should not truncate short messages', () => { + const $shortMessage = findMessage('SHORT MESSAGE'); + expect($shortMessage.data('original-message')).not.toEqual(jasmine.anything()); + }); + + it('should allow clicking a truncated message to expand and collapse the full message', () => { + const $longMessage = findMessage('LONG MESSAGE'); + $longMessage.click(); + expect($longMessage.data('original-message').length).toEqual($longMessage.text().length); + $longMessage.click(); + assertMaxLength($longMessage); + }); +}); -- cgit v1.2.1 From 025009929c3f9fb30be2860e75c8c736761a48d0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Jan 2018 10:26:48 +0100 Subject: Add link to QA page objects docs from main README --- qa/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qa/README.md b/qa/README.md index 7f2dd39ff63..8fa04e80825 100644 --- a/qa/README.md +++ b/qa/README.md @@ -27,13 +27,17 @@ following call would login to a local [GDK] instance and run all specs in bin/qa Test::Instance http://localhost:3000 ``` +### Writing tests + +1. [Using page objects](qa/page/README.md) + ### Running specific tests You can also supply specific tests to run as another parameter. For example, to -test the EE license specs, you can run: +run the repository-related specs, you can execute: ``` -EE_LICENSE="" bin/qa Test::Instance http://localhost qa/specs/features/ee +bin/qa Test::Instance http://localhost qa/specs/features/repository/ ``` Since the arguments would be passed to `rspec`, you could use all `rspec` -- cgit v1.2.1 From 355982744eb4f0fa905b8fe3257f90978823c720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 11 Jan 2018 10:46:26 +0100 Subject: Fix breadcumb of clusters show page --- app/views/projects/clusters/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index c7c84b5a42c..2049105dff6 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -1,6 +1,6 @@ - @content_class = "limit-container-width" unless fluid_layout - add_to_breadcrumbs "Clusters", project_clusters_path(@project) -- breadcrumb_title @cluster.id +- breadcrumb_title @cluster.name - page_title _("Cluster") - expanded = Rails.env.test? -- cgit v1.2.1 From 1011ca49495eb1f48d43694bfbb2a96951fd45ba Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Jan 2018 10:47:49 +0100 Subject: Add a DSL evaluator to QA base factory class --- qa/qa/factory/base.rb | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb index 00851a7bece..3e5227ea426 100644 --- a/qa/qa/factory/base.rb +++ b/qa/qa/factory/base.rb @@ -1,6 +1,12 @@ +require 'forwardable' + module QA module Factory class Base + extend SingleForwardable + + def_delegators :evaluator, :dependency, :dependencies + def fabricate!(*_args) raise NotImplementedError end @@ -17,16 +23,25 @@ module QA end end - def self.dependencies - @dependencies ||= {} + def self.evaluator + @evaluator ||= Factory::Base::DSL.new(self) end - def self.dependency(factory, as:, &block) - as.tap do |name| - class_eval { attr_accessor name } + class DSL + attr_reader :dependencies + + def initialize(base) + @base = base + @dependencies = {} + end + + def dependency(factory, as:, &block) + as.tap do |name| + @base.class_eval { attr_accessor name } - Dependency::Signature.new(factory, block).tap do |signature| - dependencies.store(name, signature) + Dependency::Signature.new(factory, block).tap do |signature| + dependencies.store(name, signature) + end end end end -- cgit v1.2.1 From d32a151714c01bc09484d423dd2136a89760b5ef Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Jan 2018 10:58:56 +0100 Subject: Make it possible to define QA factory product attributes --- qa/qa/factory/base.rb | 12 ++++++++++-- qa/qa/factory/product.rb | 2 ++ qa/spec/factory/base_spec.rb | 46 +++++++++++++++++++++++++++++--------------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb index 3e5227ea426..c7202d4ae65 100644 --- a/qa/qa/factory/base.rb +++ b/qa/qa/factory/base.rb @@ -6,6 +6,7 @@ module QA extend SingleForwardable def_delegators :evaluator, :dependency, :dependencies + def_delegators :evaluator, :product, :attributes def fabricate!(*_args) raise NotImplementedError @@ -28,11 +29,12 @@ module QA end class DSL - attr_reader :dependencies + attr_reader :dependencies, :attributes def initialize(base) @base = base @dependencies = {} + @attributes = {} end def dependency(factory, as:, &block) @@ -40,10 +42,16 @@ module QA @base.class_eval { attr_accessor name } Dependency::Signature.new(factory, block).tap do |signature| - dependencies.store(name, signature) + @dependencies.store(name, signature) end end end + + def product(attribute, &block) + Product::Attribute.new(attribute, block).tap do |signature| + @attributes.store(attribute, signature) + end + end end end end diff --git a/qa/qa/factory/product.rb b/qa/qa/factory/product.rb index df35bbbb443..2558350f8a3 100644 --- a/qa/qa/factory/product.rb +++ b/qa/qa/factory/product.rb @@ -5,6 +5,8 @@ module QA class Product include Capybara::DSL + Attribute = Struct.new(:name, :block) + def initialize(factory) @factory = factory @location = current_url diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb index a3ba0176819..d7cb9a770b5 100644 --- a/qa/spec/factory/base_spec.rb +++ b/qa/spec/factory/base_spec.rb @@ -59,30 +59,44 @@ describe QA::Factory::Base do it 'defines dependency accessors' do expect(subject.new).to respond_to :mydep, :mydep= end - end - describe 'building dependencies' do - let(:dependency) { double('dependency') } - let(:instance) { spy('instance') } + describe 'dependencies fabrication' do + let(:dependency) { double('dependency') } + let(:instance) { spy('instance') } - subject do - Class.new(described_class) do - dependency Some::MyDependency, as: :mydep + subject do + Class.new(described_class) do + dependency Some::MyDependency, as: :mydep + end end - end - before do - stub_const('Some::MyDependency', dependency) + before do + stub_const('Some::MyDependency', dependency) + + allow(subject).to receive(:new).and_return(instance) + allow(instance).to receive(:mydep).and_return(nil) + allow(QA::Factory::Product).to receive(:new) + end - allow(subject).to receive(:new).and_return(instance) - allow(instance).to receive(:mydep).and_return(nil) - allow(QA::Factory::Product).to receive(:new) + it 'builds all dependencies first' do + expect(dependency).to receive(:fabricate!).once + + subject.fabricate! + end end + end - it 'builds all dependencies first' do - expect(dependency).to receive(:fabricate!).once + describe '.product' do + subject do + Class.new(described_class) do + product :token do |factory, page| + page.do_something! + end + end + end - subject.fabricate! + it 'appends new product attribute' do + expect(subject.attributes).to be_one end end end -- cgit v1.2.1 From cf8429867021f20e671c02115b9c6806acd2d739 Mon Sep 17 00:00:00 2001 From: Fabio Busatto Date: Thu, 11 Jan 2018 09:59:00 +0000 Subject: Update links for GCP instructions --- app/views/projects/clusters/gcp/_header.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml index e2d7326a312..bddb902115d 100644 --- a/app/views/projects/clusters/gcp/_header.html.haml +++ b/app/views/projects/clusters/gcp/_header.html.haml @@ -4,11 +4,11 @@ = s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:') %ul %li - - link_to_kubernetes_engine = link_to(s_('ClusterIntegration|access to Google Kubernetes Engine'), 'https://console.cloud.google.com', target: '_blank', rel: 'noopener noreferrer') + - link_to_kubernetes_engine = link_to(s_('ClusterIntegration|access to Google Kubernetes Engine'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine } %li - - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer') + - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements } %li - - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer') + - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project } -- cgit v1.2.1 From 56469fc7ec4d608db43fc0f961133236e1b38599 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 11 Jan 2018 10:28:54 +0000 Subject: Uses `callDefault` --- app/assets/javascripts/dispatcher.js | 44 +++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d0073a1a403..3b3d08d6724 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -440,10 +440,14 @@ import Activities from './activities'; break; case 'admin:groups:create': case 'admin:groups:new': - import('./pages/admin/groups/new').then(m => m.default()).catch(fail); + import('./pages/admin/groups/new') + .then(callDefault) + .catch(fail); break; case 'admin:groups:edit': - import('./pages/admin/groups/edit').then(m => m.default()).catch(fail); + import('./pages/admin/groups/edit') + .then(callDefault) + .catch(fail); break; case 'groups:edit': groupAvatar(); @@ -562,7 +566,9 @@ import Activities from './activities'; import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail); break; case 'admin:impersonation_tokens:index': - import('./pages/admin/impersonation_tokens').then(m => m.default()).catch(fail); + import('./pages/admin/impersonation_tokens') + .then(callDefault) + .catch(fail); break; case 'profiles:personal_access_tokens:index': new DueDateSelectors(); @@ -599,35 +605,51 @@ import Activities from './activities'; // needed in rspec gl.u2fAuthenticate = u2fAuthenticate; case 'admin': - import('./pages/admin').then(m => m.default()).catch(fail); + import('./pages/admin') + .then(callDefault) + .catch(fail); switch (path[1]) { case 'broadcast_messages': - import('./pages/admin/broadcast_messages').then(m => m.default()).catch(fail); + import('./pages/admin/broadcast_messages') + .then(callDefault) + .catch(fail); break; case 'cohorts': - import('./pages/admin/cohorts').then(m => m.default()).catch(fail); + import('./pages/admin/cohorts') + .then(callDefault) + .catch(fail); break; case 'groups': switch (path[2]) { case 'show': - import('./pages/admin/groups/show').then(m => m.default()).catch(fail); + import('./pages/admin/groups/show') + .then(callDefault) + .catch(fail); break; } break; case 'projects': - import('./pages/admin/projects').then(m => m.default()).catch(fail); + import('./pages/admin/projects') + .then(callDefault) + .catch(fail); break; case 'labels': switch (path[2]) { case 'new': - import('./pages/admin/labels/new').then(m => m.default()).catch(fail); + import('./pages/admin/labels/new') + .then(callDefault) + .catch(fail); break; case 'edit': - import('./pages/admin/labels/edit').then(m => m.default()).catch(fail); + import('./pages/admin/labels/edit') + .then(callDefault) + .catch(fail); break; } case 'abuse_reports': - import('./pages/admin/abuse_reports').then(m => m.default()).catch(fail); + import('./pages/admin/abuse_reports') + .then(callDefault) + .catch(fail); break; } break; -- cgit v1.2.1 From a3ab8aa3bbcf83c80e8f6195994bd531507e7348 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Jan 2018 12:00:22 +0100 Subject: Populate QA factory product with data from a page --- qa/qa/factory/base.rb | 4 +++- qa/qa/factory/product.rb | 15 ++++++++------- qa/spec/factory/base_spec.rb | 28 ++++++++++++++++++++++++---- qa/spec/factory/product_spec.rb | 21 ++------------------- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb index c7202d4ae65..bd66b74a164 100644 --- a/qa/qa/factory/base.rb +++ b/qa/qa/factory/base.rb @@ -13,7 +13,7 @@ module QA end def self.fabricate!(*args) - Factory::Product.populate!(new) do |factory| + new.tap do |factory| yield factory if block_given? dependencies.each do |name, signature| @@ -21,6 +21,8 @@ module QA end factory.fabricate!(*args) + + return Factory::Product.populate!(self) end end diff --git a/qa/qa/factory/product.rb b/qa/qa/factory/product.rb index 2558350f8a3..d004e642f9b 100644 --- a/qa/qa/factory/product.rb +++ b/qa/qa/factory/product.rb @@ -7,8 +7,7 @@ module QA Attribute = Struct.new(:name, :block) - def initialize(factory) - @factory = factory + def initialize @location = current_url end @@ -17,11 +16,13 @@ module QA end def self.populate!(factory) - raise ArgumentError unless block_given? - - yield factory - - new(factory) + new.tap do |product| + factory.attributes.each_value do |attribute| + product.instance_exec(&attribute.block).tap do |value| + product.define_singleton_method(attribute.name) { value } + end + end + end end end end diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb index d7cb9a770b5..90dd58e20fd 100644 --- a/qa/spec/factory/base_spec.rb +++ b/qa/spec/factory/base_spec.rb @@ -1,8 +1,9 @@ describe QA::Factory::Base do + let(:factory) { spy('factory') } + let(:product) { spy('product') } + describe '.fabricate!' do subject { Class.new(described_class) } - let(:factory) { spy('factory') } - let(:product) { spy('product') } before do allow(QA::Factory::Product).to receive(:new).and_return(product) @@ -89,14 +90,33 @@ describe QA::Factory::Base do describe '.product' do subject do Class.new(described_class) do - product :token do |factory, page| - page.do_something! + product :token do + page.do_something_on_page! + 'resulting value' end end end it 'appends new product attribute' do expect(subject.attributes).to be_one + expect(subject.attributes).to have_key(:token) + end + + describe 'populating fabrication product with data' do + let(:page) { spy('page') } + + before do + allow(subject).to receive(:new).and_return(factory) + allow(QA::Factory::Product).to receive(:new).and_return(product) + allow(product).to receive(:page).and_return(page) + end + + it 'populates product after fabrication' do + subject.fabricate! + + expect(page).to have_received(:do_something_on_page!) + expect(product.token).to eq 'resulting value' + end end end end diff --git a/qa/spec/factory/product_spec.rb b/qa/spec/factory/product_spec.rb index 3d9e86a641b..fdfb1ec90cc 100644 --- a/qa/spec/factory/product_spec.rb +++ b/qa/spec/factory/product_spec.rb @@ -3,19 +3,8 @@ describe QA::Factory::Product do let(:product) { spy('product') } describe '.populate!' do - it 'instantiates and yields factory' do - expect(described_class).to receive(:new).with(factory) - - described_class.populate!(factory) do |instance| - instance.something = 'string' - end - - expect(factory).to have_received(:something=).with('string') - end - it 'returns a fabrication product' do - expect(described_class).to receive(:new) - .with(factory).and_return(product) + expect(described_class).to receive(:new).and_return(product) result = described_class.populate!(factory) do |instance| instance.something = 'string' @@ -23,11 +12,6 @@ describe QA::Factory::Product do expect(result).to be product end - - it 'raises unless block given' do - expect { described_class.populate!(factory) } - .to raise_error ArgumentError - end end describe '.visit!' do @@ -37,8 +21,7 @@ describe QA::Factory::Product do allow_any_instance_of(described_class) .to receive(:visit).and_return('visited some url') - expect(described_class.new(factory).visit!) - .to eq 'visited some url' + expect(subject.visit!).to eq 'visited some url' end end end -- cgit v1.2.1 From a2ae0f85127619e0e380c9cd5198508a801ab3bd Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Wed, 10 Jan 2018 12:56:03 +0530 Subject: Add modal dialog for leave group action --- app/assets/javascripts/groups/components/app.vue | 48 ++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index 400306759b2..e035ba462db 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -1,16 +1,20 @@ @@ -29,6 +39,8 @@ :title="title" :data-clipboard-text="text" v-tooltip + :data-container="tooltipContainer" + :data-placement="tooltipPlacement" >