From ae09f8a70a2e848c4a5fc70ac4ccff1bc3874eed Mon Sep 17 00:00:00 2001 From: Filipe Freire Date: Fri, 12 Jan 2018 09:45:02 +0000 Subject: gitlab-qa !155 push creates event on activity page --- qa/qa/page/activity/activity.rb | 18 ++++++++++++++++++ qa/qa/specs/features/activity/activity_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 qa/qa/page/activity/activity.rb create mode 100644 qa/qa/specs/features/activity/activity_spec.rb diff --git a/qa/qa/page/activity/activity.rb b/qa/qa/page/activity/activity.rb new file mode 100644 index 00000000000..fc885deedf1 --- /dev/null +++ b/qa/qa/page/activity/activity.rb @@ -0,0 +1,18 @@ +module QA + module Page + module Activity + class Activity < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#155 + # + view 'app/views/projects/activity.html.haml' + + def go_to_push_events + click_button 'Push events' + end + end + end + end +end diff --git a/qa/qa/specs/features/activity/activity_spec.rb b/qa/qa/specs/features/activity/activity_spec.rb new file mode 100644 index 00000000000..5eab95c95f3 --- /dev/null +++ b/qa/qa/specs/features/activity/activity_spec.rb @@ -0,0 +1,24 @@ +module QA + feature 'activity page', :core do + scenario 'push creates an event in the activity page' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + Factory::Resource::Project.fabricate! do |project| + project.name = 'awesome-project' + project.description = 'create awesome project test' + end + + Factory::Repository::Push.fabricate! do |push| + push.file_name = 'README.md' + push.file_content = '# This is a test project' + push.commit_message = 'Add README.md' + end + + Page::Activity::Activity.act { go_to_push_events } + + expect(page).to have_content('Add README.md') + expect(page).to have_content('pushed to branch master') + end + end +end -- cgit v1.2.1 From 669c34db6c75d826b6b692232f235f9dcc492082 Mon Sep 17 00:00:00 2001 From: Filipe Freire Date: Fri, 12 Jan 2018 10:22:35 +0000 Subject: Add relevant element for push event button --- qa/qa/page/activity/activity.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/qa/qa/page/activity/activity.rb b/qa/qa/page/activity/activity.rb index fc885deedf1..7970d8186c6 100644 --- a/qa/qa/page/activity/activity.rb +++ b/qa/qa/page/activity/activity.rb @@ -2,15 +2,12 @@ module QA module Page module Activity class Activity < Page::Base - ## - # TODO, define all selectors required by this page object - # - # See gitlab-org/gitlab-qa#155 - # - view 'app/views/projects/activity.html.haml' + view 'app/views/projects/activity.html.haml' do + element :push_events_button, 'Push events' + end def go_to_push_events - click_button 'Push events' + click_button :push_events_button end end end -- cgit v1.2.1 From d0a4ab21f0927fa602e1bab972f545e29d8cf206 Mon Sep 17 00:00:00 2001 From: Filipe Freire Date: Fri, 12 Jan 2018 16:45:00 +0000 Subject: improvements after CR --- qa/qa/page/activity/activity.rb | 15 --------------- qa/qa/page/project/activity.rb | 19 +++++++++++++++++++ qa/qa/specs/features/activity/activity_spec.rb | 24 ------------------------ qa/qa/specs/features/project/activity_spec.rb | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 39 deletions(-) delete mode 100644 qa/qa/page/activity/activity.rb create mode 100644 qa/qa/page/project/activity.rb delete mode 100644 qa/qa/specs/features/activity/activity_spec.rb create mode 100644 qa/qa/specs/features/project/activity_spec.rb diff --git a/qa/qa/page/activity/activity.rb b/qa/qa/page/activity/activity.rb deleted file mode 100644 index 7970d8186c6..00000000000 --- a/qa/qa/page/activity/activity.rb +++ /dev/null @@ -1,15 +0,0 @@ -module QA - module Page - module Activity - class Activity < Page::Base - view 'app/views/projects/activity.html.haml' do - element :push_events_button, 'Push events' - end - - def go_to_push_events - click_button :push_events_button - end - end - end - end -end diff --git a/qa/qa/page/project/activity.rb b/qa/qa/page/project/activity.rb new file mode 100644 index 00000000000..c343266f01d --- /dev/null +++ b/qa/qa/page/project/activity.rb @@ -0,0 +1,19 @@ +module QA + module Page + module Project + class Activity < Page::Base + view 'app/views/shared/_event_filter.html.haml' do + element :push_events_button, 'Push events' + end + + def go_to_push_events + click_link :push_events_button + end + + def self.path + '/activity' + end + end + end + end +end diff --git a/qa/qa/specs/features/activity/activity_spec.rb b/qa/qa/specs/features/activity/activity_spec.rb deleted file mode 100644 index 5eab95c95f3..00000000000 --- a/qa/qa/specs/features/activity/activity_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -module QA - feature 'activity page', :core do - scenario 'push creates an event in the activity page' do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } - - Factory::Resource::Project.fabricate! do |project| - project.name = 'awesome-project' - project.description = 'create awesome project test' - end - - Factory::Repository::Push.fabricate! do |push| - push.file_name = 'README.md' - push.file_content = '# This is a test project' - push.commit_message = 'Add README.md' - end - - Page::Activity::Activity.act { go_to_push_events } - - expect(page).to have_content('Add README.md') - expect(page).to have_content('pushed to branch master') - end - end -end diff --git a/qa/qa/specs/features/project/activity_spec.rb b/qa/qa/specs/features/project/activity_spec.rb new file mode 100644 index 00000000000..3dd82bb0587 --- /dev/null +++ b/qa/qa/specs/features/project/activity_spec.rb @@ -0,0 +1,19 @@ +module QA + feature 'activity page', :core do + scenario 'push creates an event in the activity page' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + Factory::Repository::Push.fabricate! do |push| + push.file_name = 'README.md' + push.file_content = '# This is a test project' + push.commit_message = 'Add README.md' + end + + Page::Project::Activity.act { go_to_push_events } + + expect(page).to have_content('Add README.md') + expect(page).to have_content('pushed to branch master') + end + end +end -- cgit v1.2.1 From a1a5d142981379087ca7183d402300a3a3b6ad52 Mon Sep 17 00:00:00 2001 From: Filipe Freire Date: Fri, 12 Jan 2018 17:43:13 +0000 Subject: improvement after CR - test reaches Activity page - the actual git push seems to be failing - had problems with defining selectors --- qa/qa.rb | 2 ++ qa/qa/page/project/activity.rb | 15 +++++++-------- qa/qa/page/project/sidebar.rb | 18 ++++++++++++++++++ qa/qa/specs/features/project/activity_spec.rb | 2 ++ 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 qa/qa/page/project/sidebar.rb diff --git a/qa/qa.rb b/qa/qa.rb index 4803432aeee..5562d59abe4 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -100,6 +100,8 @@ module QA module Project autoload :New, 'qa/page/project/new' autoload :Show, 'qa/page/project/show' + autoload :Sidebar, 'qa/page/project/sidebar' + autoload :Activity, 'qa/page/project/activity' module Settings autoload :Common, 'qa/page/project/settings/common' diff --git a/qa/qa/page/project/activity.rb b/qa/qa/page/project/activity.rb index c343266f01d..47bfca087fb 100644 --- a/qa/qa/page/project/activity.rb +++ b/qa/qa/page/project/activity.rb @@ -2,16 +2,15 @@ module QA module Page module Project class Activity < Page::Base - view 'app/views/shared/_event_filter.html.haml' do - element :push_events_button, 'Push events' - end + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#155 + # + view 'app/views/shared/_event_filter.html.haml' def go_to_push_events - click_link :push_events_button - end - - def self.path - '/activity' + click_on 'Push events' end end end diff --git a/qa/qa/page/project/sidebar.rb b/qa/qa/page/project/sidebar.rb new file mode 100644 index 00000000000..3e4f783f105 --- /dev/null +++ b/qa/qa/page/project/sidebar.rb @@ -0,0 +1,18 @@ +module QA + module Page + module Project + class Sidebar < Page::Base + ## + # TODO, define all selectors required by this page object + # + # See gitlab-org/gitlab-qa#155 + # + view 'app/views/layouts/nav/sidebar/_project.html.haml' + + def go_to_activity + click_on class: 'shortcuts-project-activity' + end + end + end + end +end diff --git a/qa/qa/specs/features/project/activity_spec.rb b/qa/qa/specs/features/project/activity_spec.rb index 3dd82bb0587..8934a61b76b 100644 --- a/qa/qa/specs/features/project/activity_spec.rb +++ b/qa/qa/specs/features/project/activity_spec.rb @@ -10,6 +10,8 @@ module QA push.commit_message = 'Add README.md' end + Page::Project::Sidebar.act { go_to_activity } + Page::Project::Activity.act { go_to_push_events } expect(page).to have_content('Add README.md') -- cgit v1.2.1 From d7caa2f8ccf554cdd0763db0cca1dc1c39ba30df Mon Sep 17 00:00:00 2001 From: Filipe Freire Date: Tue, 16 Jan 2018 20:20:57 +0000 Subject: improvements after CR + move activity click to side.rb + add element check on activity.rb + minor fixes --- qa/qa.rb | 1 - qa/qa/page/menu/side.rb | 5 +++++ qa/qa/page/project/activity.rb | 13 +++++++------ qa/qa/page/project/sidebar.rb | 18 ------------------ qa/qa/specs/features/project/activity_spec.rb | 5 ++--- 5 files changed, 14 insertions(+), 28 deletions(-) delete mode 100644 qa/qa/page/project/sidebar.rb diff --git a/qa/qa.rb b/qa/qa.rb index 5562d59abe4..2247098b997 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -100,7 +100,6 @@ module QA module Project autoload :New, 'qa/page/project/new' autoload :Show, 'qa/page/project/show' - autoload :Sidebar, 'qa/page/project/sidebar' autoload :Activity, 'qa/page/project/activity' module Settings diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 1df4e0c2429..3361fd188cb 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -6,6 +6,7 @@ module QA element :settings_item element :repository_link, "title: 'Repository'" element :top_level_items, '.sidebar-top-level-items' + element :activity_link, "title: 'Activity'" end def click_repository_setting @@ -29,6 +30,10 @@ module QA yield end end + + def go_to_activity + click_on class: 'shortcuts-project-activity' + end end end end diff --git a/qa/qa/page/project/activity.rb b/qa/qa/page/project/activity.rb index 47bfca087fb..d766dbfb8f6 100644 --- a/qa/qa/page/project/activity.rb +++ b/qa/qa/page/project/activity.rb @@ -2,12 +2,13 @@ module QA module Page module Project class Activity < Page::Base - ## - # TODO, define all selectors required by this page object - # - # See gitlab-org/gitlab-qa#155 - # - view 'app/views/shared/_event_filter.html.haml' + view 'app/views/shared/_event_filter.html.haml' do + ## + # TODO, This needs improvement, _event_filter.html.haml + # doesn't have proper elements defined + # + element :push_events, '.event-filter' + end def go_to_push_events click_on 'Push events' diff --git a/qa/qa/page/project/sidebar.rb b/qa/qa/page/project/sidebar.rb deleted file mode 100644 index 3e4f783f105..00000000000 --- a/qa/qa/page/project/sidebar.rb +++ /dev/null @@ -1,18 +0,0 @@ -module QA - module Page - module Project - class Sidebar < Page::Base - ## - # TODO, define all selectors required by this page object - # - # See gitlab-org/gitlab-qa#155 - # - view 'app/views/layouts/nav/sidebar/_project.html.haml' - - def go_to_activity - click_on class: 'shortcuts-project-activity' - end - end - end - end -end diff --git a/qa/qa/specs/features/project/activity_spec.rb b/qa/qa/specs/features/project/activity_spec.rb index 8934a61b76b..ba94ce8cf28 100644 --- a/qa/qa/specs/features/project/activity_spec.rb +++ b/qa/qa/specs/features/project/activity_spec.rb @@ -10,12 +10,11 @@ module QA push.commit_message = 'Add README.md' end - Page::Project::Sidebar.act { go_to_activity } + Page::Menu::Side.act { go_to_activity } Page::Project::Activity.act { go_to_push_events } - expect(page).to have_content('Add README.md') - expect(page).to have_content('pushed to branch master') + expect(page).to have_content('pushed new branch master') end end end -- cgit v1.2.1 From 4f4e123e2ecbf2f3c0284e10c625ca8a832de0e2 Mon Sep 17 00:00:00 2001 From: Tom Elliff Date: Wed, 17 Jan 2018 14:27:06 +0000 Subject: Fix docs for GIT_CHECKOUT Gitlab Runner configuration Should fix https://gitlab.com/gitlab-org/gitlab-runner/issues/2652 --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ae0b5c0a2ba..56eb65c003a 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1293,7 +1293,7 @@ to the CI pipeline: ```yaml variables: GIT_STRATEGY: clone - GIT_CHECKOUT: false + GIT_CHECKOUT: "false" script: - git checkout master - git merge $CI_BUILD_REF_NAME -- cgit v1.2.1 From 80e224c829183109b339205b86ded31b6aca47eb Mon Sep 17 00:00:00 2001 From: Filipe Freire Date: Thu, 18 Jan 2018 18:38:02 +0000 Subject: improve interaction with activity on side.rb --- qa/qa/page/menu/side.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 3361fd188cb..00b7eba62b0 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -32,7 +32,9 @@ module QA end def go_to_activity - click_on class: 'shortcuts-project-activity' + within_sidebar do + click_on 'Activity' + end end end end -- cgit v1.2.1 From 350dbca41c2be6717d4c9f5800ef6dd60b06d932 Mon Sep 17 00:00:00 2001 From: Filipe Freire Date: Thu, 18 Jan 2018 18:52:03 +0000 Subject: improve push events selector in activity.rb --- qa/qa/page/project/activity.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/qa/qa/page/project/activity.rb b/qa/qa/page/project/activity.rb index d766dbfb8f6..0196922c889 100644 --- a/qa/qa/page/project/activity.rb +++ b/qa/qa/page/project/activity.rb @@ -3,11 +3,7 @@ module QA module Project class Activity < Page::Base view 'app/views/shared/_event_filter.html.haml' do - ## - # TODO, This needs improvement, _event_filter.html.haml - # doesn't have proper elements defined - # - element :push_events, '.event-filter' + element :push_events, "event_filter_link EventFilter.push, _('Push events')" end def go_to_push_events -- cgit v1.2.1 From 39a659a1350553900c0efe500da752665dd4a376 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 18 Jan 2018 22:23:35 +0800 Subject: Scrap the deploy key title and match it in test --- qa/qa/factory/resource/deploy_key.rb | 6 ++++++ qa/qa/page/project/settings/deploy_keys.rb | 6 ++++-- qa/qa/specs/features/project/add_deploy_key_spec.rb | 12 ++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb index 7c58e70bcc4..671114d38b1 100644 --- a/qa/qa/factory/resource/deploy_key.rb +++ b/qa/qa/factory/resource/deploy_key.rb @@ -4,6 +4,12 @@ module QA class DeployKey < Factory::Base attr_accessor :title, :key + product :title do + Page::Project::Settings::Repository.act do + expand_deploy_keys(&:key_title) + end + end + dependency Factory::Resource::Project, as: :project do |project| project.name = 'project-to-deploy' project.description = 'project for adding deploy key test' diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb index bf42767c707..211699599e1 100644 --- a/qa/qa/page/project/settings/deploy_keys.rb +++ b/qa/qa/page/project/settings/deploy_keys.rb @@ -29,9 +29,11 @@ module QA click_on 'Add key' end - def has_key_title?(title) + def key_title page.within('.deploy-keys') do - page.find('.title', text: title) + # The instance might have public keys available, here we want + # the one from the project + page.first('.title').text end end end diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb index 43a85213501..7a123e539e1 100644 --- a/qa/qa/specs/features/project/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb @@ -7,16 +7,12 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Factory::Resource::DeployKey.fabricate! do |deploy_key| - deploy_key.title = deploy_key_title - deploy_key.key = deploy_key_value + deploy_key = Factory::Resource::DeployKey.fabricate! do |resource| + resource.title = deploy_key_title + resource.key = deploy_key_value end - Page::Project::Settings::Repository.perform do |setting| - setting.expand_deploy_keys do |page| - expect(page).to have_key_title(deploy_key_title) - end - end + expect(deploy_key.title).to eq(deploy_key_title) end end end -- cgit v1.2.1 From 09e9a50272c516997e41afff81d98d05a8b6c728 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 19 Jan 2018 22:34:21 +0800 Subject: Add .qa-project-deploy-keys to locate it Without this, it's very hard to locate it. --- app/assets/javascripts/deploy_keys/components/app.vue | 1 + qa/qa/page/project/settings/deploy_keys.rb | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index 7b68b19de75..5a782237b7d 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -87,6 +87,7 @@
Date: Thu, 18 Jan 2018 17:07:37 +0000 Subject: Generate ssh key on the fly for QA --- qa/Gemfile | 1 + qa/Gemfile.lock | 4 +++- qa/qa.rb | 1 + qa/qa/runtime/rsa_key.rb | 21 +++++++++++++++++++++ qa/qa/runtime/user.rb | 11 ----------- qa/qa/specs/features/project/add_deploy_key_spec.rb | 2 +- 6 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 qa/qa/runtime/rsa_key.rb diff --git a/qa/Gemfile b/qa/Gemfile index 4c866a3f893..1cc40f2d2d0 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -6,3 +6,4 @@ gem 'capybara-screenshot', '~> 1.0.18' gem 'rake', '~> 12.3.0' gem 'rspec', '~> 3.7' gem 'selenium-webdriver', '~> 3.8.0' +gem 'net-ssh', require: false diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 88d5fe834a0..7aa442b8c8e 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -24,6 +24,7 @@ GEM method_source (0.9.0) mini_mime (1.0.0) mini_portile2 (2.3.0) + net-ssh (4.1.0) nokogiri (1.8.1) mini_portile2 (~> 2.3.0) pry (0.11.3) @@ -63,10 +64,11 @@ PLATFORMS DEPENDENCIES capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) + net-ssh pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) selenium-webdriver (~> 3.8.0) BUNDLED WITH - 1.16.0 + 1.16.1 diff --git a/qa/qa.rb b/qa/qa.rb index 4803432aeee..b8821f2c164 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -11,6 +11,7 @@ module QA autoload :Scenario, 'qa/runtime/scenario' autoload :Browser, 'qa/runtime/browser' autoload :Env, 'qa/runtime/env' + autoload :RSAKey, 'qa/runtime/rsa_key' end ## diff --git a/qa/qa/runtime/rsa_key.rb b/qa/qa/runtime/rsa_key.rb new file mode 100644 index 00000000000..e9b6ea66dbd --- /dev/null +++ b/qa/qa/runtime/rsa_key.rb @@ -0,0 +1,21 @@ +require 'net/ssh' +require 'forwardable' + +module QA + module Runtime + class RSAKey + extend Forwardable + + def initialize(bits = 4096) + @key = OpenSSL::PKey::RSA.new(bits) + end + + attr_reader :key + def_delegators :@key, :fingerprint + + def public_key + @public_key ||= "#{key.ssh_type} #{[key.to_blob].pack('m0')}" + end + end + end +end diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb index 2832439d9e0..60027c89ab1 100644 --- a/qa/qa/runtime/user.rb +++ b/qa/qa/runtime/user.rb @@ -10,17 +10,6 @@ module QA def password ENV['GITLAB_PASSWORD'] || '5iveL!fe' end - - def ssh_key - <<~KEY.delete("\n") - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9 - 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5 - /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7 - M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC - rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0 - 5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com - KEY - end end end end diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb index 43a85213501..abff641ccce 100644 --- a/qa/qa/specs/features/project/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb @@ -1,7 +1,7 @@ module QA feature 'deploy keys support', :core do given(:deploy_key_title) { 'deploy key title' } - given(:deploy_key_value) { Runtime::User.ssh_key } + given(:deploy_key_value) { Runtime::RSAKey.new.public_key } scenario 'user adds a deploy key' do Runtime::Browser.visit(:gitlab, Page::Main::Login) -- cgit v1.2.1 From 4b1da8502f4625116447014b18adc7e1986e038e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 22 Jan 2018 16:25:14 +0800 Subject: Also test if the fingerprint is correct --- .../javascripts/deploy_keys/components/key.vue | 4 ++-- qa/qa/factory/resource/deploy_key.rb | 6 ++++++ qa/qa/page/base.rb | 16 +++++++++++++++- qa/qa/page/project/settings/deploy_keys.rb | 22 ++++++++++++++++++---- .../specs/features/project/add_deploy_key_spec.rb | 4 +++- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index 843564ce016..c6091efd62f 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -53,10 +53,10 @@
- + {{ deployKey.title }} -
+
{{ deployKey.fingerprint }}
diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb index 671114d38b1..d161e57ec99 100644 --- a/qa/qa/factory/resource/deploy_key.rb +++ b/qa/qa/factory/resource/deploy_key.rb @@ -10,6 +10,12 @@ module QA end end + product :fingerprint do + Page::Project::Settings::Repository.act do + expand_deploy_keys(&:key_fingerprint) + end + end + dependency Factory::Resource::Project, as: :project do |project| project.name = 'project-to-deploy' project.description = 'project for adding deploy key test' diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index ea4c920c82c..81ba80cdbaf 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -41,7 +41,21 @@ module QA end def click_element(name) - find(Page::Element.new(name).selector_css).click + find_element(name).click + end + + def find_element(name) + find(element_selector_css(name)) + end + + def within_element(name) + page.within(element_selector_css(name)) do + yield + end + end + + def element_selector_css(name) + Page::Element.new(name).selector_css end def self.path diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb index f9e40bf4252..332e84724c7 100644 --- a/qa/qa/page/project/settings/deploy_keys.rb +++ b/qa/qa/page/project/settings/deploy_keys.rb @@ -14,8 +14,8 @@ module QA end view 'app/assets/javascripts/deploy_keys/components/key.vue' do - element :key_title, /class=".*title.*"/ - element :key_title_field, '{{ deployKey.title }}' + element :key_title, /class=".*qa-key-title.*"/ + element :key_fingerprint, /class=".*qa-key-fingerprint.*"/ end def fill_key_title(title) @@ -31,8 +31,22 @@ module QA end def key_title - page.within('.qa-project-deploy-keys') do - page.find('.title').text + within_project_deploy_keys do + find_element(:key_title).text + end + end + + def key_fingerprint + within_project_deploy_keys do + find_element(:key_fingerprint).text + end + end + + private + + def within_project_deploy_keys + within_element(:project_deploy_keys) do + yield end end end diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb index 54852bfce4d..67d302a5593 100644 --- a/qa/qa/specs/features/project/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb @@ -1,7 +1,8 @@ module QA feature 'deploy keys support', :core do + given(:key) { Runtime::RSAKey.new } given(:deploy_key_title) { 'deploy key title' } - given(:deploy_key_value) { Runtime::RSAKey.new.public_key } + given(:deploy_key_value) { key.public_key } scenario 'user adds a deploy key' do Runtime::Browser.visit(:gitlab, Page::Main::Login) @@ -13,6 +14,7 @@ module QA end expect(deploy_key.title).to eq(deploy_key_title) + expect(deploy_key.fingerprint).to eq(key.fingerprint) end end end -- cgit v1.2.1 From d0b08f1c50b0b4106c0ae5f398912aab596a447a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 22 Jan 2018 09:59:55 +0100 Subject: Add GitLab Runner service to GitLab QA --- qa/qa.rb | 9 ++++++--- qa/qa/service/omnibus.rb | 20 ++++++++++++++++++++ qa/qa/service/runner.rb | 28 ++++++++++++++++++++++++++++ qa/qa/service/shellout.rb | 23 +++++++++++++++++++++++ qa/qa/shell/omnibus.rb | 39 --------------------------------------- 5 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 qa/qa/service/omnibus.rb create mode 100644 qa/qa/service/runner.rb create mode 100644 qa/qa/service/shellout.rb delete mode 100644 qa/qa/shell/omnibus.rb diff --git a/qa/qa.rb b/qa/qa.rb index 4803432aeee..5c277b95517 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -126,10 +126,13 @@ module QA end ## - # Classes describing shell interaction with GitLab + # Classes describing services being part of GitLab and how we can interact + # with these services, like through the shell. # - module Shell - autoload :Omnibus, 'qa/shell/omnibus' + module Service + autoload :Shellable, 'qa/service/shellable' + autoload :Omnibus, 'qa/service/omnibus' + autoload :Runner, 'qa/service/runner' end ## diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb new file mode 100644 index 00000000000..b5c06874e5c --- /dev/null +++ b/qa/qa/service/omnibus.rb @@ -0,0 +1,20 @@ +module QA + module Service + class Omnibus + include Scenario::Actable + include Service::Shellout + + def initialize(container) + @name = container + end + + def gitlab_ctl(command, input: nil) + if input.nil? + shell "docker exec #{@name} gitlab-ctl #{command}" + else + shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'" + end + end + end + end +end diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb new file mode 100644 index 00000000000..a6a60f53787 --- /dev/null +++ b/qa/qa/service/runner.rb @@ -0,0 +1,28 @@ +module QA + module Service + class Runner + include Scenario::Actable + include Service::Shellout + + def initialize(image) + @image = image + end + + def pull + shell "docker pull #{@image}" + end + + def register(token) + raise NotImplementedError + end + + def run + raise NotImplementedError + end + + def remove + raise NotImplementedError + end + end + end +end diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb new file mode 100644 index 00000000000..898febde63c --- /dev/null +++ b/qa/qa/service/shellout.rb @@ -0,0 +1,23 @@ +require 'open3' + +module QA + module Service + module Shellout + ## + # TODO, make it possible to use generic QA framework classes + # as a library - gitlab-org/gitlab-qa#94 + # + def shell(command) + puts "Executing `#{command}`" + + Open3.popen2e(command) do |_in, out, wait| + out.each { |line| puts line } + + if wait.value.exited? && wait.value.exitstatus.nonzero? + raise "Command `#{command}` failed!" + end + end + end + end + end +end diff --git a/qa/qa/shell/omnibus.rb b/qa/qa/shell/omnibus.rb deleted file mode 100644 index 6b3628d3109..00000000000 --- a/qa/qa/shell/omnibus.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'open3' - -module QA - module Shell - class Omnibus - include Scenario::Actable - - def initialize(container) - @name = container - end - - def gitlab_ctl(command, input: nil) - if input.nil? - shell "docker exec #{@name} gitlab-ctl #{command}" - else - shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'" - end - end - - private - - ## - # TODO, make it possible to use generic QA framework classes - # as a library - gitlab-org/gitlab-qa#94 - # - def shell(command) - puts "Executing `#{command}`" - - Open3.popen2e(command) do |_in, out, wait| - out.each { |line| puts line } - - if wait.value.exited? && wait.value.exitstatus.nonzero? - raise "Docker command `#{command}` failed!" - end - end - end - end - end -end -- cgit v1.2.1 From 1c0437d95e6329035af49d13b26c4a60948b02e3 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 2 Jan 2018 09:23:39 +0100 Subject: Client implementation for WikiPageVerion Part of gitlab-org/gitaly#639 --- app/controllers/projects/wikis_controller.rb | 4 +- lib/gitlab/git/wiki.rb | 38 ++++++++++++++----- lib/gitlab/gitaly_client/wiki_service.rb | 56 +++++++++++++++++++++++++--- spec/models/wiki_page_spec.rb | 32 ++++++++++++---- 4 files changed, 105 insertions(+), 25 deletions(-) diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 292e4158f8b..730f20bc086 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -76,9 +76,9 @@ class Projects::WikisController < Projects::ApplicationController @page = @project_wiki.find_page(params[:id]) if @page - @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]), + @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i), total_count: @page.count_versions) - .page(params[:page]) + .page(params[:page]) else redirect_to( project_wiki_path(@project, :home), diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index d4a53d32c28..305bc9b66b0 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -93,11 +93,23 @@ module Gitlab # :per_page - The number of items per page. # :limit - Total number of items to return. def page_versions(page_path, options = {}) - current_page = gollum_page_by_path(page_path) + puts '-' * 80 + puts options + puts '-' * 80 + puts - commits_from_page(current_page, options).map do |gitlab_git_commit| - gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id) - Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format) + byebug + @repository.gitaly_migrate(:wiki_page_versions) do |is_enabled| + if is_enabled + gitaly_wiki_client.page_versions(page_path, pagination_params(options)) + else + current_page = gollum_page_by_path(page_path) + + commits_from_page(current_page, options).map do |gitlab_git_commit| + gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id) + Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format) + end + end end end @@ -124,15 +136,21 @@ module Gitlab # :per_page - The number of items per page. # :limit - Total number of items to return. def commits_from_page(gollum_page, options = {}) - unless options[:limit] - options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page - options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i - end + pagination_options = pagination_params(options) @repository.log(ref: gollum_page.last_version.id, path: gollum_page.path, - limit: options[:limit], - offset: options[:offset]) + limit: pagination_options[:limit], + offset: pagination_options[:offset]) + end + + def pagination_params(options) + return options if options[:limit] + + options = options.dup + options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page + options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i + options end def gollum_wiki diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb index 5c5b170a3e0..fa7e416504d 100644 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ b/lib/gitlab/gitaly_client/wiki_service.rb @@ -101,6 +101,48 @@ module Gitlab pages end + # options: + # :page - The Integer page number. + # :per_page - The number of items per page. + # :limit - Total number of items to return. + def page_versions(page_path, options) + request = Gitaly::WikiGetPageVersionsRequest.new( + repository: @gitaly_repo, + page_path: encode_binary(page_path) + ) + + min_index = options[:offset].to_i + max_index = min_index + options[:limit].to_i + byebug + + stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request) + + version_index = 0 + versions = [] + + # Allow limit and offset to be send to Gitaly: TODO + stream.each do |message| + puts '§' * 80 + puts version_index + + message.versions.each do |version| + case version_index + when min_index...max_index + versions << new_wiki_page_version(version) + when max_index..Float::INFINITY + return versions + end + + version_index += 1 + puts version_index + end + end + + # when we're requesting page 99 but the stream doesn't go that far, whatever + # is fetched thus far + versions + end + def find_file(name, revision) request = Gitaly::WikiFindFileRequest.new( repository: @gitaly_repo, @@ -129,7 +171,7 @@ module Gitlab private - # If a block is given and the yielded value is true, iteration will be + # If a block is given and the yielded value is truthy, iteration will be # stopped early at that point; else the iterator is consumed entirely. # The iterator is traversed with `next` to allow resuming the iteration. def wiki_page_from_iterator(iterator) @@ -146,10 +188,7 @@ module Gitlab else wiki_page = GitalyClient::WikiPage.new(page.to_h) - version = Gitlab::Git::WikiPageVersion.new( - Gitlab::Git::Commit.decorate(@repository, page.version.commit), - page.version.format - ) + version = new_wiki_page_version(page.version) end end @@ -158,6 +197,13 @@ module Gitlab [wiki_page, version] end + def new_wiki_page_version(version) + Gitlab::Git::WikiPageVersion.new( + Gitlab::Git::Commit.decorate(@repository, version.commit), + version.format + ) + end + def gitaly_commit_details(commit_details) Gitaly::WikiCommitDetails.new( name: encode_binary(commit_details.name), diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index ea75434e399..835ac20c5d8 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -252,18 +252,34 @@ describe WikiPage do end describe "#versions" do - before do - create_page("Update", "content") - @page = wiki.find_page("Update") + shared_examples 'wiki page versions' do + let(:page) { wiki.find_page("Update") } + + before do + create_page("Update", "content") + end + + after do + destroy_page("Update") + end + + it "returns an array of all commits for the page" do + 3.times { |i| page.update(content: "content #{i}") } + + expect(page.versions.count).to eq(4) + end + + it 'returns instances of WikiPageVersion' do + expect(page.versions).to all( be_a(Gitlab::Git::WikiPageVersion) ) + end end - after do - destroy_page("Update") + context 'when Gitaly is enabled' do + it_behaves_like 'wiki page versions' end - it "returns an array of all commits for the page" do - 3.times { |i| @page.update(content: "content #{i}") } - expect(@page.versions.count).to eq(4) + context 'when Gitaly is disabled', :disable_gitaly do + it_behaves_like 'wiki page versions' end end -- cgit v1.2.1 From bd96f2d6e351229b888bf4f41f39a082d2ac16cd Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 16 Jan 2018 10:42:00 +0100 Subject: Upgrade Gitaly versions --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 6f82890d0d1..a4c3db4bf5f 100644 --- a/Gemfile +++ b/Gemfile @@ -406,7 +406,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.73.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.74.0', require: 'gitaly' gem 'toml-rb', '~> 0.3.15', require: false -- cgit v1.2.1 From ca7e1058e8d91a01157ac7d9170423e16a732850 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 22 Jan 2018 18:22:44 +0800 Subject: Add a scenario for adding secret variables --- app/views/projects/settings/ci_cd/show.html.haml | 4 +- qa/qa.rb | 3 ++ qa/qa/factory/resource/secret_variable.rb | 43 ++++++++++++++++ qa/qa/page/menu/side.rb | 20 +++++++- qa/qa/page/project/settings/cicd.rb | 21 ++++++++ qa/qa/page/project/settings/secret_variables.rb | 60 ++++++++++++++++++++++ .../features/project/add_secret_variable_spec.rb | 19 +++++++ 7 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 qa/qa/factory/resource/secret_variable.rb create mode 100644 qa/qa/page/project/settings/cicd.rb create mode 100644 qa/qa/page/project/settings/secret_variables.rb create mode 100644 qa/qa/specs/features/project/add_secret_variable_spec.rb diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 664a4554692..32d8eebfa32 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -26,12 +26,12 @@ .settings-content = render 'projects/runners/index' -%section.settings.no-animate{ class: ('expanded' if expanded) } +%section.settings.no-animate.qa-secret-variables{ class: ('expanded' if expanded) } .settings-header %h4 Secret variables = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank' - %button.btn.js-settings-toggle + %button.btn.js-settings-toggle.qa-expand-secret-variables = expanded ? 'Collapse' : 'Expand' %p = render "ci/variables/content" diff --git a/qa/qa.rb b/qa/qa.rb index 4803432aeee..ac082cfe9a0 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -26,6 +26,7 @@ module QA autoload :Group, 'qa/factory/resource/group' autoload :Project, 'qa/factory/resource/project' autoload :DeployKey, 'qa/factory/resource/deploy_key' + autoload :SecretVariable, 'qa/factory/resource/secret_variable' end module Repository @@ -105,6 +106,8 @@ module QA autoload :Common, 'qa/page/project/settings/common' autoload :Repository, 'qa/page/project/settings/repository' autoload :DeployKeys, 'qa/page/project/settings/deploy_keys' + autoload :CICD, 'qa/page/project/settings/cicd' + autoload :SecretVariables, 'qa/page/project/settings/secret_variables' end end diff --git a/qa/qa/factory/resource/secret_variable.rb b/qa/qa/factory/resource/secret_variable.rb new file mode 100644 index 00000000000..26f27f25a2e --- /dev/null +++ b/qa/qa/factory/resource/secret_variable.rb @@ -0,0 +1,43 @@ +module QA + module Factory + module Resource + class SecretVariable < Factory::Base + attr_accessor :key, :value + + product :key do + Page::Project::Settings::CICD.act do + expand_secret_variables(&:variable_key) + end + end + + product :value do + Page::Project::Settings::CICD.act do + expand_secret_variables(&:variable_value) + end + end + + dependency Factory::Resource::Project, as: :project do |project| + project.name = 'project-with-secret-variables' + project.description = 'project for adding secret variable test' + end + + def fabricate! + project.visit! + + Page::Menu::Side.act do + click_cicd_setting + end + + Page::Project::Settings::CICD.perform do |setting| + setting.expand_secret_variables do |page| + page.fill_variable_key(key) + page.fill_variable_value(value) + + page.add_variable + end + end + end + end + end + end +end diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 1df4e0c2429..0053d5056e1 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -8,19 +8,31 @@ module QA element :top_level_items, '.sidebar-top-level-items' end + view 'app/assets/javascripts/fly_out_nav.js' do + element :fly_out, "IS_SHOWING_FLY_OUT_CLASS = 'is-showing-fly-out'" + end + def click_repository_setting hover_setting do click_link('Repository') end end + def click_cicd_setting + hover_setting do + click_link('CI / CD') + end + end + private def hover_setting within_sidebar do find('.qa-settings-item').hover - yield + within_fly_out do + yield + end end end @@ -29,6 +41,12 @@ module QA yield end end + + def within_fly_out + page.within('.is-showing-fly-out') do + yield + end + end end end end diff --git a/qa/qa/page/project/settings/cicd.rb b/qa/qa/page/project/settings/cicd.rb new file mode 100644 index 00000000000..045a0826522 --- /dev/null +++ b/qa/qa/page/project/settings/cicd.rb @@ -0,0 +1,21 @@ +module QA + module Page + module Project + module Settings + class CICD < Page::Base + include Common + + view 'app/views/projects/settings/ci_cd/show.html.haml' do + element :expand_secret_variables + end + + def expand_secret_variables(&block) + expand(:expand_secret_variables) do + SecretVariables.perform(&block) + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/settings/secret_variables.rb b/qa/qa/page/project/settings/secret_variables.rb new file mode 100644 index 00000000000..880ef58ab54 --- /dev/null +++ b/qa/qa/page/project/settings/secret_variables.rb @@ -0,0 +1,60 @@ +module QA + module Page + module Project + module Settings + class SecretVariables < Page::Base + view 'app/views/ci/variables/_table.html.haml' do + element :variable_key, '.variable-key' + element :variable_value, '.variable-value' + end + + view 'app/views/projects/settings/ci_cd/show.html.haml' do + element :secret_variable + end + + def fill_variable_key(key) + fill_in 'variable_key', with: key + end + + def fill_variable_value(value) + fill_in 'variable_value', with: value + end + + def add_variable + click_on 'Add new variable' + end + + def variable_key + page.find('.variable-key').text + end + + def variable_value + reveal_value do + page.find('.variable-value').text + end + end + + private + + def within_section + page.within('.qa-secret-variables') do + yield + end + end + + def reveal_value + within_section do + click_button('Reveal value') + end + + yield.tap do + within_section do + click_button('Hide value') + end + end + end + end + end + end + end +end diff --git a/qa/qa/specs/features/project/add_secret_variable_spec.rb b/qa/qa/specs/features/project/add_secret_variable_spec.rb new file mode 100644 index 00000000000..803ced6e3fc --- /dev/null +++ b/qa/qa/specs/features/project/add_secret_variable_spec.rb @@ -0,0 +1,19 @@ +module QA + feature 'secret variables support', :core do + given(:variable_key) { 'VARIABLE_KEY' } + given(:variable_value) { 'variable value' } + + scenario 'user adds a secret variable' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + variable = Factory::Resource::SecretVariable.fabricate! do |resource| + resource.key = variable_key + resource.value = variable_value + end + + expect(variable.key).to eq(variable_key) + expect(variable.value).to eq(variable_value) + end + end +end -- cgit v1.2.1 From 50a82f64e53d396ca91f52c4d25af60beb52a424 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 22 Jan 2018 14:03:03 +0100 Subject: Add end-to-end test for registering GitLab Runner --- qa/qa.rb | 5 ++- qa/qa/factory/resource/deploy_key.rb | 2 +- qa/qa/factory/resource/runner.rb | 52 ++++++++++++++++++++++++++ qa/qa/page/menu/side.rb | 25 +++++++++++-- qa/qa/page/project/settings/ci_cd.rb | 21 +++++++++++ qa/qa/page/project/settings/common.rb | 10 +++++ qa/qa/page/project/settings/runners.rb | 19 ++++++++++ qa/qa/service/runner.rb | 31 ++++++++++----- qa/qa/specs/features/project/pipelines_spec.rb | 17 +++++++++ 9 files changed, 166 insertions(+), 16 deletions(-) create mode 100644 qa/qa/factory/resource/runner.rb create mode 100644 qa/qa/page/project/settings/ci_cd.rb create mode 100644 qa/qa/page/project/settings/runners.rb create mode 100644 qa/qa/specs/features/project/pipelines_spec.rb diff --git a/qa/qa.rb b/qa/qa.rb index 5c277b95517..24738b5531a 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -26,6 +26,7 @@ module QA autoload :Group, 'qa/factory/resource/group' autoload :Project, 'qa/factory/resource/project' autoload :DeployKey, 'qa/factory/resource/deploy_key' + autoload :Runner, 'qa/factory/resource/runner' end module Repository @@ -104,7 +105,9 @@ module QA module Settings autoload :Common, 'qa/page/project/settings/common' autoload :Repository, 'qa/page/project/settings/repository' + autoload :CICD, 'qa/page/project/settings/ci_cd' autoload :DeployKeys, 'qa/page/project/settings/deploy_keys' + autoload :Runners, 'qa/page/project/settings/runners' end end @@ -130,7 +133,7 @@ module QA # with these services, like through the shell. # module Service - autoload :Shellable, 'qa/service/shellable' + autoload :Shellout, 'qa/service/shellout' autoload :Omnibus, 'qa/service/omnibus' autoload :Runner, 'qa/service/runner' end diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb index 7c58e70bcc4..d51701e65ea 100644 --- a/qa/qa/factory/resource/deploy_key.rb +++ b/qa/qa/factory/resource/deploy_key.rb @@ -13,7 +13,7 @@ module QA project.visit! Page::Menu::Side.act do - click_repository_setting + click_repository_settings end Page::Project::Settings::Repository.perform do |setting| diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb new file mode 100644 index 00000000000..037136dd113 --- /dev/null +++ b/qa/qa/factory/resource/runner.rb @@ -0,0 +1,52 @@ +require 'securerandom' + +module QA + module Factory + module Resource + class Runner < Factory::Base + attr_writer :name + + dependency Factory::Resource::Project, as: :project do |project| + project.name = 'project-with-ci-cd' + project.description = 'Project with CI/CD Pipelines' + end + + def name + @name || "qa-runner-#{SecureRandom.hex(4)}" + end + + def perform(&block) + @block ||= block + end + + def fabricate! + project.visit! + + Page::Menu::Side.act { click_ci_cd_settings } + + Service::Runner.perform do |runner| + Page::Project::Settings::CICD.perform do |settings| + settings.expand_runners_settings do |runners| + runner.pull + runner.name = name + runner.token = runners.registration_token + runner.address = runners.coordinator_address + runner.tags = %w[qa test] + runner.register! + end + + sleep 5 # TODO, non-blocking waiting for Runner to register. + + settings.refresh + + settings.expand_runners_settings do |runners| + perform&.call(runners) + runner.remove! + end + end + end + end + end + end + end +end diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 1df4e0c2429..e666d570172 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -5,18 +5,29 @@ module QA view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :settings_item element :repository_link, "title: 'Repository'" + element :repository_link, "title: 'CI / CD'" element :top_level_items, '.sidebar-top-level-items' end - def click_repository_setting - hover_setting do - click_link('Repository') + def click_repository_settings + hover_settings do + within_submenu do + click_link('Repository') + end + end + end + + def click_ci_cd_settings + hover_settings do + within_submenu do + click_link('CI / CD') + end end end private - def hover_setting + def hover_settings within_sidebar do find('.qa-settings-item').hover @@ -29,6 +40,12 @@ module QA yield end end + + def within_submenu + page.within('.fly-out-list') do + yield + end + end end end end diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb new file mode 100644 index 00000000000..5270dde7411 --- /dev/null +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -0,0 +1,21 @@ +module QA + module Page + module Project + module Settings + class CICD < Page::Base + include Common + + view 'app/views/projects/settings/ci_cd/show.html.haml' do + element :runners_settings, 'Runners settings' + end + + def expand_runners_settings(&block) + expand_section('Runners settings') do + Settings::Runners.perform(&block) + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb index b4ef07e1540..1357bf031d5 100644 --- a/qa/qa/page/project/settings/common.rb +++ b/qa/qa/page/project/settings/common.rb @@ -10,6 +10,16 @@ module QA yield end end + + def expand_section(name) + page.within('#content-body') do + page.within('section', text: name) do + click_button 'Expand' + + yield + end + end + end end end end diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb new file mode 100644 index 00000000000..ba3dc632c59 --- /dev/null +++ b/qa/qa/page/project/settings/runners.rb @@ -0,0 +1,19 @@ +module QA + module Page + module Project + module Settings + class Runners < Page::Base + def registration_token + find('code#registration_token').text + end + + def coordinator_address + # TODO, this needs a specific ID or QA class + # + all('code').first.text + end + end + end + end + end +end diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb index a6a60f53787..a9906b8627a 100644 --- a/qa/qa/service/runner.rb +++ b/qa/qa/service/runner.rb @@ -1,27 +1,38 @@ +require 'securerandom' + module QA module Service class Runner include Scenario::Actable include Service::Shellout - def initialize(image) - @image = image + attr_writer :token, :address, :tags, :image, :name + + def initialize + @image = 'gitlab/gitlab-runner:alpine' + @name = "gitlab-runner-qa-#{SecureRandom.hex(4)}" end def pull shell "docker pull #{@image}" end - def register(token) - raise NotImplementedError - end - - def run - raise NotImplementedError + def register! + shell <<~CMD.tr("\n", ' ') + docker run -d --rm --entrypoint=/bin/sh + --network test --name #{@name} + -e CI_SERVER_URL=#{@address} + -e REGISTER_NON_INTERACTIVE=true + -e REGISTRATION_TOKEN=#{@token} + -e RUNNER_EXECUTOR=shell + -e RUNNER_TAG_LIST=#{@tags.to_a.join(',')} + -e RUNNER_NAME=#{@name} + #{@image} -c 'gitlab-runner register && gitlab-runner run' + CMD end - def remove - raise NotImplementedError + def remove! + shell "docker rm -f #{@name}" end end end diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb new file mode 100644 index 00000000000..1897b2fa27c --- /dev/null +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -0,0 +1,17 @@ +module QA + feature 'CI/CD Pipelines', :core, :docker do + scenario 'user registers a new specific runner' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + Factory::Resource::Runner.fabricate! do |runner| + runner.name = 'my-qa-runner' + + runner.perform do |page| + expect(page).to have_content('my-qa-runner') + expect(page).to have_css('.runner-status-online') + end + end + end + end +end -- cgit v1.2.1 From ca4db9d25f797a07bed3750ca9d8f0833487f55c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 22 Jan 2018 14:12:36 +0100 Subject: Wait for Runner to register in QA and DRY page objects --- qa/qa/factory/resource/runner.rb | 9 ++++++--- qa/qa/page/project/settings/runners.rb | 4 ++++ qa/qa/service/runner.rb | 8 ++++++-- qa/qa/specs/features/project/pipelines_spec.rb | 8 +++----- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb index 037136dd113..9d11f3c572f 100644 --- a/qa/qa/factory/resource/runner.rb +++ b/qa/qa/factory/resource/runner.rb @@ -35,12 +35,15 @@ module QA runner.register! end - sleep 5 # TODO, non-blocking waiting for Runner to register. - + ## + # TODO, refactor to support non-blocking wait time until + # GitLab Runner sucessfully registers itself. + # + sleep 5 settings.refresh settings.expand_runners_settings do |runners| - perform&.call(runners) + perform&.call(runners, runner) runner.remove! end end diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb index ba3dc632c59..ac93c3efddd 100644 --- a/qa/qa/page/project/settings/runners.rb +++ b/qa/qa/page/project/settings/runners.rb @@ -12,6 +12,10 @@ module QA # all('code').first.text end + + def has_online_runner? + page.has_css?('.runner-status-online') + end end end end diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb index a9906b8627a..2745bf00ded 100644 --- a/qa/qa/service/runner.rb +++ b/qa/qa/service/runner.rb @@ -6,11 +6,11 @@ module QA include Scenario::Actable include Service::Shellout - attr_writer :token, :address, :tags, :image, :name + attr_accessor :token, :address, :tags, :image, :name def initialize @image = 'gitlab/gitlab-runner:alpine' - @name = "gitlab-runner-qa-#{SecureRandom.hex(4)}" + @name = "qa-runner-#{SecureRandom.hex(4)}" end def pull @@ -18,6 +18,10 @@ module QA end def register! + ## + # TODO, this assumes that `test` network exists, because we know that + # gitlab-qa environment orchestration tool creates it. + # shell <<~CMD.tr("\n", ' ') docker run -d --rm --entrypoint=/bin/sh --network test --name #{@name} diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index 1897b2fa27c..84e5a0e93cf 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -5,11 +5,9 @@ module QA Page::Main::Login.act { sign_in_using_credentials } Factory::Resource::Runner.fabricate! do |runner| - runner.name = 'my-qa-runner' - - runner.perform do |page| - expect(page).to have_content('my-qa-runner') - expect(page).to have_css('.runner-status-online') + runner.perform do |page, runner| + expect(page).to have_content(runner.name) + expect(page).to have_online_runner end end end -- cgit v1.2.1 From a29f0c28fd07ba14f0d0e5fb9c878a2eb117e388 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 16 Jan 2018 11:12:08 +0100 Subject: Allow pagination for WikiVersions on Gitaly request --- Gemfile.lock | 4 ++-- lib/gitlab/git/wiki.rb | 32 +++++++++++++------------------- lib/gitlab/gitaly_client/wiki_service.rb | 26 ++++---------------------- 3 files changed, 19 insertions(+), 43 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 284e1e7654d..d69c532b309 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -285,7 +285,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.73.0) + gitaly-proto (0.74.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -1056,7 +1056,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.73.0) + gitaly-proto (~> 0.74.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index 305bc9b66b0..4ed78daa443 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -93,15 +93,15 @@ module Gitlab # :per_page - The number of items per page. # :limit - Total number of items to return. def page_versions(page_path, options = {}) - puts '-' * 80 - puts options - puts '-' * 80 - puts - - byebug @repository.gitaly_migrate(:wiki_page_versions) do |is_enabled| if is_enabled - gitaly_wiki_client.page_versions(page_path, pagination_params(options)) + versions = gitaly_wiki_client.page_versions(page_path, options) + + # Gitaly uses gollum-lib to get the versions. Gollum defaults to 20 + # per page, but also fetches 20 if `limit` or `per_page` < 20. + # Slicing returns an array with the expected number of items. + slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page + versions[0..slice_bound] else current_page = gollum_page_by_path(page_path) @@ -136,21 +136,15 @@ module Gitlab # :per_page - The number of items per page. # :limit - Total number of items to return. def commits_from_page(gollum_page, options = {}) - pagination_options = pagination_params(options) + unless options[:limit] + options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page + options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i + end @repository.log(ref: gollum_page.last_version.id, path: gollum_page.path, - limit: pagination_options[:limit], - offset: pagination_options[:offset]) - end - - def pagination_params(options) - return options if options[:limit] - - options = options.dup - options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page - options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i - options + limit: options[:limit], + offset: options[:offset]) end def gollum_wiki diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb index fa7e416504d..a98c3c0b160 100644 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ b/lib/gitlab/gitaly_client/wiki_service.rb @@ -108,38 +108,20 @@ module Gitlab def page_versions(page_path, options) request = Gitaly::WikiGetPageVersionsRequest.new( repository: @gitaly_repo, - page_path: encode_binary(page_path) + page_path: encode_binary(page_path), + page: options[:page] || 1, + per_page: options[:per_page] || Gollum::Page.per_page ) - min_index = options[:offset].to_i - max_index = min_index + options[:limit].to_i - byebug - stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request) - version_index = 0 versions = [] - - # Allow limit and offset to be send to Gitaly: TODO stream.each do |message| - puts '§' * 80 - puts version_index - message.versions.each do |version| - case version_index - when min_index...max_index - versions << new_wiki_page_version(version) - when max_index..Float::INFINITY - return versions - end - - version_index += 1 - puts version_index + versions << new_wiki_page_version(version) end end - # when we're requesting page 99 but the stream doesn't go that far, whatever - # is fetched thus far versions end -- cgit v1.2.1 From 5520e3ebc329cdfec4bf61aa29dd25de563c3e43 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 22 Jan 2018 15:18:19 +0100 Subject: Reduce nesting and simplify CI/CD end-to-end tests --- qa/qa/factory/resource/runner.rb | 21 ++--------- qa/qa/service/runner.rb | 6 +-- qa/qa/specs/features/project/pipelines_spec.rb | 52 +++++++++++++++++++++++++- qa/spec/factory/base_spec.rb | 1 - 4 files changed, 56 insertions(+), 24 deletions(-) diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb index 9d11f3c572f..299a9d0716d 100644 --- a/qa/qa/factory/resource/runner.rb +++ b/qa/qa/factory/resource/runner.rb @@ -15,36 +15,21 @@ module QA @name || "qa-runner-#{SecureRandom.hex(4)}" end - def perform(&block) - @block ||= block - end - def fabricate! project.visit! Page::Menu::Side.act { click_ci_cd_settings } - Service::Runner.perform do |runner| + Service::Runner.new(name).tap do |runner| Page::Project::Settings::CICD.perform do |settings| settings.expand_runners_settings do |runners| runner.pull - runner.name = name runner.token = runners.registration_token runner.address = runners.coordinator_address runner.tags = %w[qa test] runner.register! - end - - ## - # TODO, refactor to support non-blocking wait time until - # GitLab Runner sucessfully registers itself. - # - sleep 5 - settings.refresh - - settings.expand_runners_settings do |runners| - perform&.call(runners, runner) - runner.remove! + # TODO, wait for runner to register using non-blocking method. + sleep 5 end end end diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb index 2745bf00ded..2e313b59d28 100644 --- a/qa/qa/service/runner.rb +++ b/qa/qa/service/runner.rb @@ -6,11 +6,11 @@ module QA include Scenario::Actable include Service::Shellout - attr_accessor :token, :address, :tags, :image, :name + attr_accessor :token, :address, :tags, :image - def initialize + def initialize(name) @image = 'gitlab/gitlab-runner:alpine' - @name = "qa-runner-#{SecureRandom.hex(4)}" + @name = name || "qa-runner-#{SecureRandom.hex(4)}" end def pull diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index 84e5a0e93cf..56a6acee6a4 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -1,15 +1,63 @@ module QA feature 'CI/CD Pipelines', :core, :docker do + let(:executor) { "qa-runner-#{Time.now.to_i}" } + + after do + Service::Runner.new(executor).remove! + end + scenario 'user registers a new specific runner' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } Factory::Resource::Runner.fabricate! do |runner| - runner.perform do |page, runner| - expect(page).to have_content(runner.name) + runner.name = executor + end + + Page::Project::Settings::CICD.perform do |settings| + settings.expand_runners_settings do |page| + expect(page).to have_content(runner) expect(page).to have_online_runner end end end + + scenario 'users creates a new pipeline' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + project = Factory::Resource::Project.fabricate! do |project| + project.name = 'project-with-pipelines' + project.description = 'Project with CI/CD Pipelines.' + end + + Factory::Resource::Runner.fabricate! do |runner| + runner.project = project + runner.name = executor + end + + Factory::Repository::Push.fabricate! do |push| + push.project = project + push.file_name = '.gitlab-ci.yml' + push.commit_message = 'Add .gitlab-ci.yml' + push.file_content = <<~EOF + echo-success-test: + script: echo 'OK' + + echo-failure-test: + script: + - echo 'FAILURE' + - exit 1 + + echo-artifacts-test: + script: echo "CONTENTS" > my-artifacts/artifact.txt + artifacts: + paths: + - my-artifacts/ + EOF + end + + Page::Project::Show.act { wait_for_push } + end end end diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb index 90dd58e20fd..c5663049be8 100644 --- a/qa/spec/factory/base_spec.rb +++ b/qa/spec/factory/base_spec.rb @@ -19,7 +19,6 @@ describe QA::Factory::Base do it 'returns fabrication product' do allow(subject).to receive(:new).and_return(factory) - allow(factory).to receive(:fabricate!).and_return('something') result = subject.fabricate!('something') -- cgit v1.2.1 From 738bad8ee83932ffd1b7b4de9113b0152b37172a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 22 Jan 2018 15:31:41 +0100 Subject: Fix CI/CD end-to-end tests by waiting for changes --- qa/qa/page/project/show.rb | 1 + qa/qa/specs/features/project/pipelines_spec.rb | 6 +++++- qa/qa/specs/features/repository/push_spec.rb | 5 +---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index c8af5ba6280..5e66e40a0b5 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -33,6 +33,7 @@ module QA def wait_for_push sleep 5 + refresh end end end diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index 56a6acee6a4..f9280cd049a 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -15,8 +15,10 @@ module QA end Page::Project::Settings::CICD.perform do |settings| + settings.refresh + settings.expand_runners_settings do |page| - expect(page).to have_content(runner) + expect(page).to have_content(executor) expect(page).to have_online_runner end end @@ -58,6 +60,8 @@ module QA end Page::Project::Show.act { wait_for_push } + + expect(page).to have_content('Add .gitlab-ci.yml') end end end diff --git a/qa/qa/specs/features/repository/push_spec.rb b/qa/qa/specs/features/repository/push_spec.rb index 4f6ffe14c9f..51d9c2c7fd2 100644 --- a/qa/qa/specs/features/repository/push_spec.rb +++ b/qa/qa/specs/features/repository/push_spec.rb @@ -11,10 +11,7 @@ module QA push.commit_message = 'Add README.md' end - Page::Project::Show.act do - wait_for_push - refresh - end + Page::Project::Show.act { wait_for_push } expect(page).to have_content('README.md') expect(page).to have_content('This is a test project') -- cgit v1.2.1 From 57749eca898ca12546313068f2492ab4d2d0afe2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 22 Jan 2018 17:09:08 -0600 Subject: Refactor dispatcher milestones show path --- app/assets/javascripts/dispatcher.js | 11 +++++++---- app/assets/javascripts/pages/groups/milestones/show/index.js | 3 +++ app/assets/javascripts/pages/init_milestones_show.js | 9 +++++++++ .../javascripts/pages/projects/milestones/show/index.js | 3 +++ 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/pages/groups/milestones/show/index.js create mode 100644 app/assets/javascripts/pages/init_milestones_show.js create mode 100644 app/assets/javascripts/pages/projects/milestones/show/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index a8855df502b..8ab159da3c5 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,9 +1,7 @@ /* 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 Milestone from './milestone'; import notificationsDropdown from './notifications_dropdown'; import LineHighlighter from './line_highlighter'; import MergeRequest from './merge_request'; -import Sidebar from './right_sidebar'; import Flash from './flash'; import BlobViewer from './blob/viewer/index'; import GfmAutoComplete from './gfm_auto_complete'; @@ -92,9 +90,14 @@ import SearchAutocomplete from './search_autocomplete'; .catch(fail); break; case 'projects:milestones:show': + import('./pages/projects/milestones/show') + .then(callDefault) + .catch(fail); + break; case 'groups:milestones:show': - new Milestone(); - new Sidebar(); + import('./pages/groups/milestones/show') + .then(callDefault) + .catch(fail); break; case 'dashboard:milestones:show': import('./pages/dashboard/milestones/show') diff --git a/app/assets/javascripts/pages/groups/milestones/show/index.js b/app/assets/javascripts/pages/groups/milestones/show/index.js new file mode 100644 index 00000000000..0c3ce848e3d --- /dev/null +++ b/app/assets/javascripts/pages/groups/milestones/show/index.js @@ -0,0 +1,3 @@ +import initMilestonesShow from '~/pages/init_milestones_show'; + +export default initMilestonesShow; diff --git a/app/assets/javascripts/pages/init_milestones_show.js b/app/assets/javascripts/pages/init_milestones_show.js new file mode 100644 index 00000000000..7aa5be0d5b9 --- /dev/null +++ b/app/assets/javascripts/pages/init_milestones_show.js @@ -0,0 +1,9 @@ +/* eslint-disable no-new */ + +import Milestone from '~/milestone'; +import Sidebar from '~/right_sidebar'; + +export default () => { + new Milestone(); + new Sidebar(); +}; diff --git a/app/assets/javascripts/pages/projects/milestones/show/index.js b/app/assets/javascripts/pages/projects/milestones/show/index.js new file mode 100644 index 00000000000..0c3ce848e3d --- /dev/null +++ b/app/assets/javascripts/pages/projects/milestones/show/index.js @@ -0,0 +1,3 @@ +import initMilestonesShow from '~/pages/init_milestones_show'; + +export default initMilestonesShow; -- cgit v1.2.1 From d56b2ba2c7e219042d948d968974fdd67a1e0c6d Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Tue, 23 Jan 2018 16:42:42 +1100 Subject: consistent function exports for Labels edit/new pages --- app/assets/javascripts/pages/groups/activity/index.js | 2 +- app/assets/javascripts/pages/groups/labels/edit/index.js | 2 +- app/assets/javascripts/pages/groups/labels/new/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/pages/groups/activity/index.js b/app/assets/javascripts/pages/groups/activity/index.js index a3bd1b0f3e2..95faf1f1e98 100644 --- a/app/assets/javascripts/pages/groups/activity/index.js +++ b/app/assets/javascripts/pages/groups/activity/index.js @@ -1,3 +1,3 @@ import Activities from '~/activities'; -export default new Activities(); +export default () => new Activities(); diff --git a/app/assets/javascripts/pages/groups/labels/edit/index.js b/app/assets/javascripts/pages/groups/labels/edit/index.js index 8ff4b044ac7..72c5e4744ac 100644 --- a/app/assets/javascripts/pages/groups/labels/edit/index.js +++ b/app/assets/javascripts/pages/groups/labels/edit/index.js @@ -1,3 +1,3 @@ import Labels from '~/labels'; -export default new Labels(); +export default () => new Labels(); diff --git a/app/assets/javascripts/pages/groups/labels/new/index.js b/app/assets/javascripts/pages/groups/labels/new/index.js index 8ff4b044ac7..72c5e4744ac 100644 --- a/app/assets/javascripts/pages/groups/labels/new/index.js +++ b/app/assets/javascripts/pages/groups/labels/new/index.js @@ -1,3 +1,3 @@ import Labels from '~/labels'; -export default new Labels(); +export default () => new Labels(); -- cgit v1.2.1 From b33e7281ffef39058bcef4e741060a703cfaf02e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 23 Jan 2018 16:26:28 +0800 Subject: Make sure we're passing ASCII-8BIT to Gitaly when counting commit count. --- ...ersionerror-u-c124-from-utf-8-to-ascii-8bit.yml | 5 +++++ lib/gitlab/gitaly_client/commit_service.rb | 4 ++-- .../gitlab/gitaly_client/commit_service_spec.rb | 23 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml diff --git a/changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml b/changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml new file mode 100644 index 00000000000..c64bee9126e --- /dev/null +++ b/changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml @@ -0,0 +1,5 @@ +--- +title: Fix encoding issue when counting commit count +merge_request: 16637 +author: +type: fixed diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 2231371cfa7..33a8d3e5612 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -125,11 +125,11 @@ module Gitlab def commit_count(ref, options = {}) request = Gitaly::CountCommitsRequest.new( repository: @gitaly_repo, - revision: ref + revision: encode_binary(ref) ) request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present? request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present? - request.path = options[:path] if options[:path].present? + request.path = encode_binary(options[:path]) if options[:path].present? request.max_count = options[:max_count] if options[:max_count].present? GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index b2275119a04..3722a91c050 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -131,6 +131,29 @@ describe Gitlab::GitalyClient::CommitService do end end + describe '#commit_count' do + before do + expect_any_instance_of(Gitaly::CommitService::Stub) + .to receive(:count_commits) + .with(gitaly_request_with_path(storage_name, relative_path), + kind_of(Hash)) + .and_return([]) + end + + it 'sends a commit_count message' do + client.commit_count(revision) + end + + context 'with UTF-8 params strings' do + let(:revision) { "branch\u011F" } + let(:path) { "foo/\u011F.txt" } + + it 'handles string encodings correctly' do + client.commit_count(revision, path: path) + end + end + end + describe '#find_commit' do let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } it 'sends an RPC request' do -- cgit v1.2.1 From bba9e7275cd50825d8c1fe44dc9fff6fa448b7f2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 23 Jan 2018 10:02:43 +0000 Subject: Added dispatcher imports for groups webpack bundle --- app/assets/javascripts/dispatcher.js | 5 +++++ app/assets/javascripts/groups/index.js | 4 ++-- app/assets/javascripts/pages/dashboard/groups/index/index.js | 5 +++++ app/assets/javascripts/pages/explore/groups/index.js | 2 ++ app/assets/javascripts/pages/groups/show/index.js | 3 +++ app/views/dashboard/groups/_groups.html.haml | 2 ++ app/views/dashboard/groups/index.html.haml | 3 --- app/views/explore/groups/_groups.html.haml | 2 ++ app/views/explore/groups/index.html.haml | 3 --- app/views/groups/_children.html.haml | 5 ++--- config/webpack.config.js | 7 ++----- 11 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 app/assets/javascripts/pages/dashboard/groups/index/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 96e5c8c890c..464d6eddbe1 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -530,6 +530,11 @@ import SearchAutocomplete from './search_autocomplete'; .then(callDefault) .catch(fail); break; + case 'dashboard:groups:index': + import('./pages/dashboard/groups/index') + .then(callDefault) + .catch(fail); + break; } switch (path[0]) { case 'sessions': diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index 8b850765a1b..57eaac72906 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -10,7 +10,7 @@ import groupItemComponent from './components/group_item.vue'; Vue.use(Translate); -document.addEventListener('DOMContentLoaded', () => { +export default () => { const el = document.getElementById('js-groups-tree'); // Don't do anything if element doesn't exist (No groups) @@ -71,4 +71,4 @@ document.addEventListener('DOMContentLoaded', () => { }); }, }); -}); +}; diff --git a/app/assets/javascripts/pages/dashboard/groups/index/index.js b/app/assets/javascripts/pages/dashboard/groups/index/index.js new file mode 100644 index 00000000000..8a2aae706c0 --- /dev/null +++ b/app/assets/javascripts/pages/dashboard/groups/index/index.js @@ -0,0 +1,5 @@ +import initGroupsList from '../../../../groups'; + +export default () => { + initGroupsList(); +}; diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js index 859b073f1cb..e59c38b8bc4 100644 --- a/app/assets/javascripts/pages/explore/groups/index.js +++ b/app/assets/javascripts/pages/explore/groups/index.js @@ -1,8 +1,10 @@ import GroupsList from '~/groups_list'; import Landing from '~/landing'; +import initGroupsList from '../../../groups'; export default function () { new GroupsList(); // eslint-disable-line no-new + initGroupsList(); const landingElement = document.querySelector('.js-explore-groups-landing'); if (!landingElement) return; const exploreGroupsLanding = new Landing( diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js index 45e11b64306..6ed0f010f15 100644 --- a/app/assets/javascripts/pages/groups/show/index.js +++ b/app/assets/javascripts/pages/groups/show/index.js @@ -5,6 +5,7 @@ import notificationsDropdown from '~/notifications_dropdown'; import NotificationsForm from '~/notifications_form'; import ProjectsList from '~/projects_list'; import ShortcutsNavigation from '~/shortcuts_navigation'; +import initGroupsList from '../../../groups'; export default () => { const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup'); @@ -16,4 +17,6 @@ export default () => { if (newGroupChildWrapper) { new NewGroupChild(newGroupChildWrapper); } + + initGroupsList(); }; diff --git a/app/views/dashboard/groups/_groups.html.haml b/app/views/dashboard/groups/_groups.html.haml index 601b6a8b1a7..db856ef7d7b 100644 --- a/app/views/dashboard/groups/_groups.html.haml +++ b/app/views/dashboard/groups/_groups.html.haml @@ -1,2 +1,4 @@ .js-groups-list-holder #js-groups-tree{ data: { hide_projects: 'true', endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } } + .loading-container.text-center + = icon('spinner spin 2x', class: 'loading-animation prepend-top-20') diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index 25bf08c6c12..50f39f93283 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -3,9 +3,6 @@ - header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' -= webpack_bundle_tag 'common_vue' -= webpack_bundle_tag 'groups' - - if params[:filter].blank? && @groups.empty? = render 'shared/groups/empty_state' - else diff --git a/app/views/explore/groups/_groups.html.haml b/app/views/explore/groups/_groups.html.haml index 91149498248..ff57b39e947 100644 --- a/app/views/explore/groups/_groups.html.haml +++ b/app/views/explore/groups/_groups.html.haml @@ -1,2 +1,4 @@ .js-groups-list-holder #js-groups-tree{ data: { hide_projects: 'true', endpoint: explore_groups_path(format: :json), path: explore_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } } + .loading-container.text-center + = icon('spinner spin 2x', class: 'loading-animation prepend-top-20') diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 86abdf547cc..efa8b2706da 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -2,9 +2,6 @@ - page_title "Groups" - header_title "Groups", dashboard_groups_path -= webpack_bundle_tag 'common_vue' -= webpack_bundle_tag 'groups' - - if current_user = render 'dashboard/groups_head' - else diff --git a/app/views/groups/_children.html.haml b/app/views/groups/_children.html.haml index 3afb6b2f849..742b40784d3 100644 --- a/app/views/groups/_children.html.haml +++ b/app/views/groups/_children.html.haml @@ -1,5 +1,4 @@ -= webpack_bundle_tag 'common_vue' -= webpack_bundle_tag 'groups' - .js-groups-list-holder #js-groups-tree{ data: { hide_projects: 'false', group_id: group.id, endpoint: group_children_path(group, format: :json), path: group_path(group), form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } } + .loading-container.text-center + = icon('spinner spin 2x', class: 'loading-animation prepend-top-20') diff --git a/config/webpack.config.js b/config/webpack.config.js index 229db11acb2..dd17784f1bd 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -44,9 +44,6 @@ var config = { graphs: './graphs/graphs_bundle.js', graphs_charts: './graphs/graphs_charts.js', graphs_show: './graphs/graphs_show.js', - group: './group.js', - groups: './groups/index.js', - groups_list: './groups_list.js', help: './help/help.js', how_to_merge: './how_to_merge.js', issue_show: './issue_show/index.js', @@ -119,9 +116,9 @@ var config = { { test: /\_worker\.js$/, use: [ - { + { loader: 'worker-loader', - options: { + options: { inline: true } }, -- cgit v1.2.1 From 24659d81f3547ee0a8b7459889ecc08828d35edc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 23 Jan 2018 11:02:43 +0000 Subject: fixed group page specific JS --- app/views/shared/_group_form.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index d0b9e891b82..cb21f90696f 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -1,5 +1,3 @@ -- content_for :page_specific_javascripts do - = page_specific_javascript_bundle_tag('group') - parent = @group.parent - group_path = root_url - group_path << parent.full_path + '/' if parent -- cgit v1.2.1 From 5cb94835c0010d1954b26c858f910d533cd88e38 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 12:04:01 +0100 Subject: Refactor QA instance test scenario and tags --- qa/qa.rb | 2 +- qa/qa/scenario/entrypoint.rb | 34 --------------------- qa/qa/scenario/taggable.rb | 13 ++++++++ qa/qa/scenario/test/instance.rb | 22 ++++++++++++-- qa/qa/scenario/test/integration/mattermost.rb | 2 +- qa/qa/service/runner.rb | 10 +++--- qa/spec/scenario/entrypoint_spec.rb | 44 --------------------------- qa/spec/scenario/test/instance_spec.rb | 44 +++++++++++++++++++++++++++ 8 files changed, 83 insertions(+), 88 deletions(-) delete mode 100644 qa/qa/scenario/entrypoint.rb create mode 100644 qa/qa/scenario/taggable.rb delete mode 100644 qa/spec/scenario/entrypoint_spec.rb create mode 100644 qa/spec/scenario/test/instance_spec.rb diff --git a/qa/qa.rb b/qa/qa.rb index 24738b5531a..2766fc988a3 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -47,7 +47,7 @@ module QA # autoload :Bootable, 'qa/scenario/bootable' autoload :Actable, 'qa/scenario/actable' - autoload :Entrypoint, 'qa/scenario/entrypoint' + autoload :Taggable, 'qa/scenario/taggable' autoload :Template, 'qa/scenario/template' ## diff --git a/qa/qa/scenario/entrypoint.rb b/qa/qa/scenario/entrypoint.rb deleted file mode 100644 index ae099fd911e..00000000000 --- a/qa/qa/scenario/entrypoint.rb +++ /dev/null @@ -1,34 +0,0 @@ -module QA - module Scenario - ## - # Base class for running the suite against any GitLab instance, - # including staging and on-premises installation. - # - class Entrypoint < Template - include Bootable - - def perform(address, *files) - Runtime::Scenario.define(:gitlab_address, address) - - ## - # Perform before hooks, which are different for CE and EE - # - Runtime::Release.perform_before_hooks - - Specs::Runner.perform do |specs| - specs.tty = true - specs.tags = self.class.get_tags - specs.files = files.any? ? files : 'qa/specs/features' - end - end - - def self.tags(*tags) - @tags = tags - end - - def self.get_tags - @tags - end - end - end -end diff --git a/qa/qa/scenario/taggable.rb b/qa/qa/scenario/taggable.rb new file mode 100644 index 00000000000..b63c245bf47 --- /dev/null +++ b/qa/qa/scenario/taggable.rb @@ -0,0 +1,13 @@ +module QA + module Scenario + module Taggable + def tags(*tags) + @tags = tags + end + + def focus + @tags.to_a + end + end + end +end diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb index e2a1f6bf2bd..993bbd723a3 100644 --- a/qa/qa/scenario/test/instance.rb +++ b/qa/qa/scenario/test/instance.rb @@ -2,11 +2,29 @@ module QA module Scenario module Test ## - # Run test suite against any GitLab instance, + # Base class for running the suite against any GitLab instance, # including staging and on-premises installation. # - class Instance < Entrypoint + class Instance < Template + include Bootable + extend Taggable + tags :core + + def perform(address, *files) + Runtime::Scenario.define(:gitlab_address, address) + + ## + # Perform before hooks, which are different for CE and EE + # + Runtime::Release.perform_before_hooks + + Specs::Runner.perform do |specs| + specs.tty = true + specs.tags = self.class.focus + specs.files = files.any? ? files : 'qa/specs/features' + end + end end end end diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb index 7d0702afdb1..d939f52ab16 100644 --- a/qa/qa/scenario/test/integration/mattermost.rb +++ b/qa/qa/scenario/test/integration/mattermost.rb @@ -6,7 +6,7 @@ module QA # Run test suite against any GitLab instance where mattermost is enabled, # including staging and on-premises installation. # - class Mattermost < Scenario::Entrypoint + class Mattermost < Test::Instance tags :core, :mattermost def perform(address, mattermost, *files) diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb index 2e313b59d28..d0ee33c69f2 100644 --- a/qa/qa/service/runner.rb +++ b/qa/qa/service/runner.rb @@ -11,6 +11,8 @@ module QA def initialize(name) @image = 'gitlab/gitlab-runner:alpine' @name = name || "qa-runner-#{SecureRandom.hex(4)}" + @network = Runtime::Scenario.attributes[:network] || 'test' + @tags = %w[qa test] end def pull @@ -18,18 +20,14 @@ module QA end def register! - ## - # TODO, this assumes that `test` network exists, because we know that - # gitlab-qa environment orchestration tool creates it. - # shell <<~CMD.tr("\n", ' ') docker run -d --rm --entrypoint=/bin/sh - --network test --name #{@name} + --network #{@network} --name #{@name} -e CI_SERVER_URL=#{@address} -e REGISTER_NON_INTERACTIVE=true -e REGISTRATION_TOKEN=#{@token} -e RUNNER_EXECUTOR=shell - -e RUNNER_TAG_LIST=#{@tags.to_a.join(',')} + -e RUNNER_TAG_LIST=#{@tags.join(',')} -e RUNNER_NAME=#{@name} #{@image} -c 'gitlab-runner register && gitlab-runner run' CMD diff --git a/qa/spec/scenario/entrypoint_spec.rb b/qa/spec/scenario/entrypoint_spec.rb deleted file mode 100644 index aec79dcea04..00000000000 --- a/qa/spec/scenario/entrypoint_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -describe QA::Scenario::Entrypoint do - subject do - Class.new(QA::Scenario::Entrypoint) do - tags :rspec - end - end - - context '#perform' do - let(:arguments) { spy('Runtime::Scenario') } - let(:release) { spy('Runtime::Release') } - let(:runner) { spy('Specs::Runner') } - - before do - stub_const('QA::Runtime::Release', release) - stub_const('QA::Runtime::Scenario', arguments) - stub_const('QA::Specs::Runner', runner) - - allow(runner).to receive(:perform).and_yield(runner) - end - - it 'sets an address of the subject' do - subject.perform("hello") - - expect(arguments).to have_received(:define) - .with(:gitlab_address, "hello") - end - - context 'no paths' do - it 'should call runner with default arguments' do - subject.perform("test") - - expect(runner).to have_received(:files=).with('qa/specs/features') - end - end - - context 'specifying paths' do - it 'should call runner with paths' do - subject.perform('test', 'path1', 'path2') - - expect(runner).to have_received(:files=).with(%w[path1 path2]) - end - end - end -end diff --git a/qa/spec/scenario/test/instance_spec.rb b/qa/spec/scenario/test/instance_spec.rb new file mode 100644 index 00000000000..1824db54c9b --- /dev/null +++ b/qa/spec/scenario/test/instance_spec.rb @@ -0,0 +1,44 @@ +describe QA::Scenario::Test::Instance do + subject do + Class.new(described_class) do + tags :rspec + end + end + + context '#perform' do + let(:arguments) { spy('Runtime::Scenario') } + let(:release) { spy('Runtime::Release') } + let(:runner) { spy('Specs::Runner') } + + before do + stub_const('QA::Runtime::Release', release) + stub_const('QA::Runtime::Scenario', arguments) + stub_const('QA::Specs::Runner', runner) + + allow(runner).to receive(:perform).and_yield(runner) + end + + it 'sets an address of the subject' do + subject.perform("hello") + + expect(arguments).to have_received(:define) + .with(:gitlab_address, "hello") + end + + context 'no paths' do + it 'should call runner with default arguments' do + subject.perform("test") + + expect(runner).to have_received(:files=).with('qa/specs/features') + end + end + + context 'specifying paths' do + it 'should call runner with paths' do + subject.perform('test', 'path1', 'path2') + + expect(runner).to have_received(:files=).with(%w[path1 path2]) + end + end + end +end -- cgit v1.2.1 From 294bddf95c646e08f68aeec16024b48e18dd3ba2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 12:10:18 +0100 Subject: Use runner tags when processing CI/CD QA pipelines --- qa/qa/factory/resource/runner.rb | 8 ++++++-- qa/qa/specs/features/project/pipelines_spec.rb | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb index 299a9d0716d..109e2e59fcc 100644 --- a/qa/qa/factory/resource/runner.rb +++ b/qa/qa/factory/resource/runner.rb @@ -4,7 +4,7 @@ module QA module Factory module Resource class Runner < Factory::Base - attr_writer :name + attr_writer :name, :tags dependency Factory::Resource::Project, as: :project do |project| project.name = 'project-with-ci-cd' @@ -15,6 +15,10 @@ module QA @name || "qa-runner-#{SecureRandom.hex(4)}" end + def tags + @tags || %w[qa e2e] + end + def fabricate! project.visit! @@ -26,7 +30,7 @@ module QA runner.pull runner.token = runners.registration_token runner.address = runners.coordinator_address - runner.tags = %w[qa test] + runner.tags = tags runner.register! # TODO, wait for runner to register using non-blocking method. sleep 5 diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index f9280cd049a..3a6c93cd903 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -36,6 +36,7 @@ module QA Factory::Resource::Runner.fabricate! do |runner| runner.project = project runner.name = executor + runner.tags = %w[qa test] end Factory::Repository::Push.fabricate! do |push| @@ -44,14 +45,23 @@ module QA push.commit_message = 'Add .gitlab-ci.yml' push.file_content = <<~EOF echo-success-test: + tags: + - qa + - test script: echo 'OK' echo-failure-test: + tags: + - qa + - test script: - echo 'FAILURE' - exit 1 echo-artifacts-test: + tags: + - qa + - test script: echo "CONTENTS" > my-artifacts/artifact.txt artifacts: paths: -- cgit v1.2.1 From 2fe5d9327965cb18fe800a0c7bd3f83c44a70efb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 12:14:07 +0100 Subject: Remove blocking wait-time for runner to register in QA --- qa/qa/factory/resource/runner.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb index 109e2e59fcc..5f37f8ac2e9 100644 --- a/qa/qa/factory/resource/runner.rb +++ b/qa/qa/factory/resource/runner.rb @@ -32,8 +32,6 @@ module QA runner.address = runners.coordinator_address runner.tags = tags runner.register! - # TODO, wait for runner to register using non-blocking method. - sleep 5 end end end -- cgit v1.2.1 From c306c6da20d3422664a6fda7f4b0e680b5c3db7e Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Wed, 10 Jan 2018 01:05:50 +0200 Subject: Adjust last push notification width --- app/views/dashboard/activity.html.haml | 4 +--- app/views/dashboard/projects/index.html.haml | 3 +-- app/views/dashboard/projects/starred.html.haml | 3 +-- app/views/projects/_last_push.html.haml | 27 +++++++++++----------- app/views/projects/activity.html.haml | 3 ++- app/views/projects/blob/show.html.haml | 3 ++- app/views/projects/merge_requests/index.html.haml | 3 ++- app/views/projects/show.html.haml | 4 +++- app/views/projects/tree/show.html.haml | 3 +-- .../fix-adjust-layout-width-for-fixed-layout.yml | 5 ++++ 10 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index ad35d05c29a..31d4b3da4f1 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -7,10 +7,8 @@ - page_title "Activity" - header_title "Activity", activity_dashboard_path -.hidden-xs - = render "projects/last_push" - %div{ class: container_class } + = render "projects/last_push" = render 'dashboard/activity_head' %section.activities diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 57a4da353fe..deed774a4a5 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -7,9 +7,8 @@ - page_title "Projects" - header_title "Projects", dashboard_projects_path -= render "projects/last_push" - %div{ class: container_class } + = render "projects/last_push" - if show_projects?(@projects, params) = render 'dashboard/projects_head' = render 'nav' diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 14f9f8cd70a..b1efe59aadc 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -4,9 +4,8 @@ - page_title "Starred Projects" - header_title "Projects", dashboard_projects_path -= render "projects/last_push" - %div{ class: container_class } + = render "projects/last_push" = render 'dashboard/projects_head' - if params[:filter_projects] || any_projects?(@projects) diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index b68eb47c6b4..56eecece54c 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,19 +1,18 @@ - event = last_push_event - if event && show_last_push_widget?(event) - %div{ class: container_class } - .row-content-block.top-block.hidden-xs.white - .event-last-push - .event-last-push-text - %span= s_("LastPushEvent|You pushed to") - %strong - = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name' + .row-content-block.top-block.hidden-xs.white + .event-last-push + .event-last-push-text + %span= s_("LastPushEvent|You pushed to") + %strong + = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name' - - if event.project != @project - %span= s_("LastPushEvent|at") - %strong= link_to_project event.project + - if event.project != @project + %span= s_("LastPushEvent|at") + %strong= link_to_project event.project - #{time_ago_with_tooltip(event.created_at)} + #{time_ago_with_tooltip(event.created_at)} - .pull-right - = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do - #{ _('Create merge request') } + .pull-right + = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do + #{ _('Create merge request') } diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index d0ab39033cf..b28a375e956 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -2,6 +2,7 @@ - page_title _("Activity") -= render 'projects/last_push' +%div{ class: container_class } + = render 'projects/last_push' = render 'projects/activity' diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 4d358052d43..2ed454131af 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -6,9 +6,10 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag 'blob' -= render 'projects/last_push' %div{ class: container_class } + = render 'projects/last_push' + #tree-holder.tree-holder = render 'blob', blob: @blob diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 2ded7484151..640d2791dc1 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -10,7 +10,8 @@ = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'filtered_search' -= render 'projects/last_push' +%div{ class: container_class } + = render 'projects/last_push' - if @project.merge_requests.exists? %div{ class: container_class } diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 7a68aa16aa4..d3e867e124c 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -7,7 +7,9 @@ = render partial: 'flash_messages', locals: { project: @project } -= render "projects/last_push" +%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } + = render "projects/last_push" + = render "home_panel" - if can?(current_user, :download_code, @project) diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 709be20e00f..3b4057e56d0 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -6,7 +6,6 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") -= render 'projects/last_push' - %div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] } + = render 'projects/last_push' = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) diff --git a/changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml b/changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml new file mode 100644 index 00000000000..2e0f59f81e9 --- /dev/null +++ b/changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml @@ -0,0 +1,5 @@ +--- +title: Adjust layout width for fixed layout +merge_request: 16337 +author: George Tsiolis +type: fixed -- cgit v1.2.1 From 0711ebbd72b4e59b25bee2b0ba2444eecd3cfa7e Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 23 Jan 2018 12:42:02 +0100 Subject: Use restore() instead of reset() for removing axios MockAdapter --- doc/development/fe_guide/axios.md | 2 +- spec/javascripts/blob/notebook/index_spec.js | 6 +++--- spec/javascripts/boards/board_card_spec.js | 2 +- spec/javascripts/boards/board_list_spec.js | 2 +- spec/javascripts/boards/board_new_issue_spec.js | 2 +- spec/javascripts/boards/boards_store_spec.js | 2 +- spec/javascripts/boards/list_spec.js | 2 +- spec/javascripts/issue_show/components/app_spec.js | 2 +- spec/javascripts/jobs/job_details_mediator_spec.js | 8 ++++---- spec/javascripts/monitoring/dashboard_spec.js | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md index 34fa684c5d6..71c793a293f 100644 --- a/doc/development/fe_guide/axios.md +++ b/doc/development/fe_guide/axios.md @@ -50,7 +50,7 @@ To help us mock the responses we need we use [axios-mock-adapter][axios-mock-ada }); afterEach(() => { - mock.reset(); + mock.restore(); }); ``` diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index df1b2c9960b..a143fc827d5 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -45,7 +45,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('does not show loading icon', () => { @@ -96,7 +96,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('does not show loading icon', () => { @@ -127,7 +127,7 @@ describe('iPython notebook renderer', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('does not show loading icon', () => { diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 4e73fa1fe87..80a598e63bd 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -55,7 +55,7 @@ describe('Board card', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('returns false when detailIssue is empty', () => { diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 9ffdac9be97..a5fcb10b9dd 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -60,7 +60,7 @@ describe('Board list component', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('renders component', () => { diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js index c62c537841c..e204985f039 100644 --- a/spec/javascripts/boards/board_new_issue_spec.js +++ b/spec/javascripts/boards/board_new_issue_spec.js @@ -58,7 +58,7 @@ describe('Issue boards new issue form', () => { afterEach(() => { vm.$destroy(); - mock.reset(); + mock.restore(); }); it('calls submit if submit button is clicked', (done) => { diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 49fb20f4c84..8411f4dd8a6 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -35,7 +35,7 @@ describe('Store', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('starts with a blank state', () => { diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index e5e7b48228b..34964b20b05 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -30,7 +30,7 @@ describe('List model', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('gets issues when created', (done) => { diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 9280db072b3..1c9f48028f2 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -59,7 +59,7 @@ describe('Issuable output', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); realtimeRequestCount = 0; vm.poll.stop(); diff --git a/spec/javascripts/jobs/job_details_mediator_spec.js b/spec/javascripts/jobs/job_details_mediator_spec.js index 3069a0cd60e..ca5c9cf87e4 100644 --- a/spec/javascripts/jobs/job_details_mediator_spec.js +++ b/spec/javascripts/jobs/job_details_mediator_spec.js @@ -12,6 +12,10 @@ describe('JobMediator', () => { mock = new MockAdapter(axios); }); + afterEach(() => { + mock.restore(); + }); + it('should set defaults', () => { expect(mediator.store).toBeDefined(); expect(mediator.service).toBeDefined(); @@ -24,10 +28,6 @@ describe('JobMediator', () => { mock.onGet().reply(200, job, {}); }); - afterEach(() => { - mock.restore(); - }); - it('should store received data', (done) => { mediator.fetchJob(); setTimeout(() => { diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 9885b8a790f..eb8f6bbe50d 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -38,7 +38,7 @@ describe('Dashboard', () => { }); afterEach(() => { - mock.reset(); + mock.restore(); }); it('shows up a loading state', (done) => { -- cgit v1.2.1 From b983179545495c456db1f6c7aa4cb019b865b754 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 23 Jan 2018 11:29:00 +0000 Subject: Adds correct illustration for pending state --- app/assets/images/illustrations/pending_job_empty.svg | 1 + app/assets/stylesheets/framework/images.scss | 2 +- app/views/projects/jobs/show.html.haml | 4 ++-- changelogs/unreleased/42220-add-pending-empty-state.yml | 5 +++++ package.json | 2 +- yarn.lock | 6 +++--- 6 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 app/assets/images/illustrations/pending_job_empty.svg create mode 100644 changelogs/unreleased/42220-add-pending-empty-state.yml diff --git a/app/assets/images/illustrations/pending_job_empty.svg b/app/assets/images/illustrations/pending_job_empty.svg new file mode 100644 index 00000000000..8de695afa18 --- /dev/null +++ b/app/assets/images/illustrations/pending_job_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss index fd5c3c81a53..2d015ef086b 100644 --- a/app/assets/stylesheets/framework/images.scss +++ b/app/assets/stylesheets/framework/images.scss @@ -20,7 +20,7 @@ width: 100%; } - $image-widths: 250 306 394; + $image-widths: 250 306 394 430; @each $width in $image-widths { &.svg-#{$width} { img, diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index eb0773f2d4e..93efa7e8e86 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -103,8 +103,8 @@ content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered') - else = render 'empty_state', - illustration: 'illustrations/job_not_triggered.svg', - illustration_size: 'svg-306', + illustration: 'illustrations/pending_job_empty.svg', + illustration_size: 'svg-430', title: _('This job has not started yet'), content: _('This job is in pending state and is waiting to be picked by a runner') = render "sidebar" diff --git a/changelogs/unreleased/42220-add-pending-empty-state.yml b/changelogs/unreleased/42220-add-pending-empty-state.yml new file mode 100644 index 00000000000..ad39578f2d9 --- /dev/null +++ b/changelogs/unreleased/42220-add-pending-empty-state.yml @@ -0,0 +1,5 @@ +--- +title: Adds empty state illustration for pending job +merge_request: +author: +type: other diff --git a/package.json b/package.json index eb502ede62a..5c9849515ee 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "worker-loader": "^1.1.0" }, "devDependencies": { - "@gitlab-org/gitlab-svgs": "^1.6.0", + "@gitlab-org/gitlab-svgs": "^1.7.0", "axios-mock-adapter": "^1.10.0", "babel-plugin-istanbul": "^4.1.5", "eslint": "^3.18.0", diff --git a/yarn.lock b/yarn.lock index c3345db9c83..dac3a0f473d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,9 +54,9 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.6.0.tgz#08fa5f2e80b7ac4e4713f71fe8a684bd34430c9d" +"@gitlab-org/gitlab-svgs@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.7.0.tgz#dbb1330a1b1ee478378dddab53fe1a881e810f5d" "@types/jquery@^2.0.40": version "2.0.48" -- cgit v1.2.1 From ededa488f2c94d1d132d974476f38e668c44e4ee Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 12:55:44 +0100 Subject: Assert on pipeline jobs statuses in CI/CD QA tests --- qa/qa.rb | 5 +++++ qa/qa/page/menu/side.rb | 8 ++++++- qa/qa/page/project/pipeline/index.rb | 13 +++++++++++ qa/qa/page/project/pipeline/show.rb | 23 ++++++++++++++++++++ qa/qa/specs/features/project/pipelines_spec.rb | 30 +++++++++++++++++++++++--- 5 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 qa/qa/page/project/pipeline/index.rb create mode 100644 qa/qa/page/project/pipeline/show.rb diff --git a/qa/qa.rb b/qa/qa.rb index 2766fc988a3..b1d13ec6ab9 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -109,6 +109,11 @@ module QA autoload :DeployKeys, 'qa/page/project/settings/deploy_keys' autoload :Runners, 'qa/page/project/settings/runners' end + + module Pipeline + autoload :Index, 'qa/page/project/pipeline/index' + autoload :Show, 'qa/page/project/pipeline/show' + end end module Admin diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index e666d570172..7f0f924c5e8 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -5,7 +5,7 @@ module QA view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :settings_item element :repository_link, "title: 'Repository'" - element :repository_link, "title: 'CI / CD'" + element :pipelines_settings_link, "title: 'CI / CD'" element :top_level_items, '.sidebar-top-level-items' end @@ -25,6 +25,12 @@ module QA end end + def click_ci_cd_pipelines + within_sidebar do + click_link('CI / CD') + end + end + private def hover_settings diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb new file mode 100644 index 00000000000..32c108393b9 --- /dev/null +++ b/qa/qa/page/project/pipeline/index.rb @@ -0,0 +1,13 @@ +module QA::Page + module Project::Pipeline + class Index < QA::Page::Base + view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do + element :pipeline_link, 'class="js-pipeline-url-link"' + end + + def go_to_latest_pipeline + first('.js-pipeline-url-link').click + end + end + end +end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb new file mode 100644 index 00000000000..309d9e75ea2 --- /dev/null +++ b/qa/qa/page/project/pipeline/show.rb @@ -0,0 +1,23 @@ +module QA::Page + module Project::Pipeline + class Show < QA::Page::Base + view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do + element :pipeline_header, /header class.*ci-header-container.*/ + end + + def running? + within('.ci-header-container') do + return page.has_content?('running') + end + end + + def has_build?(name, status: :success) + within('.pipeline-graph') do + within('.ci-job-component', text: name) do + return has_selector?(".ci-status-icon-#{status}") + end + end + end + end + end +end diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index 3a6c93cd903..6760605df72 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -44,13 +44,13 @@ module QA push.file_name = '.gitlab-ci.yml' push.commit_message = 'Add .gitlab-ci.yml' push.file_content = <<~EOF - echo-success-test: + test-success: tags: - qa - test script: echo 'OK' - echo-failure-test: + test-failure: tags: - qa - test @@ -58,7 +58,13 @@ module QA - echo 'FAILURE' - exit 1 - echo-artifacts-test: + test-tags: + tags: + - qa + - docker + script: echo 'NOOP' + + test-artifacts: tags: - qa - test @@ -72,6 +78,24 @@ module QA Page::Project::Show.act { wait_for_push } expect(page).to have_content('Add .gitlab-ci.yml') + + Page::Menu::Side.act { click_ci_cd_pipelines } + + expect(page).to have_content('All 1') + expect(page).to have_content('Add .gitlab-ci.yml') + + puts 'Waiting for the runner to process the pipeline' + sleep 15 # Runner should process all jobs within 15 seconds. + + Page::Project::Pipeline::Index.act { go_to_latest_pipeline } + + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to be_running + expect(pipeline).to have_build('test-success', status: :success) + expect(pipeline).to have_build('test-failure', status: :failed) + expect(pipeline).to have_build('test-tags', status: :pending) + expect(pipeline).to have_build('test-artifacts', status: :failed) + end end end end -- cgit v1.2.1 From d327277c39df1537b41073912d9b4e9f765d8da5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 13:02:35 +0100 Subject: Add views / selectors for pipeline show page object --- qa/qa/page/project/pipeline/show.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 309d9e75ea2..0835173f1cd 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -5,6 +5,18 @@ module QA::Page element :pipeline_header, /header class.*ci-header-container.*/ end + view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do + element :pipeline_graph, /class.*pipeline-graph.*/ + end + + view 'app/assets/javascripts/pipelines/components/graph/job_component.vue' do + element :job_component, /class.*ci-job-component.*/ + end + + view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do + element :status_icon, 'ci-status-icon-${status}' + end + def running? within('.ci-header-container') do return page.has_content?('running') -- cgit v1.2.1 From 915b554773244a883790308fd9608f5f301edde0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 13:09:12 +0100 Subject: Add specific views / selectors for QA runners page --- app/views/ci/runner/_how_to_setup_runner.html.haml | 2 +- qa/qa/page/project/settings/runners.rb | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml index b75dab0acc5..8db7727b80c 100644 --- a/app/views/ci/runner/_how_to_setup_runner.html.haml +++ b/app/views/ci/runner/_how_to_setup_runner.html.haml @@ -8,7 +8,7 @@ = (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe %li = _("Specify the following URL during the Runner setup:") - %code= root_url(only_path: false) + %code#coordinator_address= root_url(only_path: false) %li = _("Use the following registration token during setup:") %code#registration_token= registration_token diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb index ac93c3efddd..ff763402d98 100644 --- a/qa/qa/page/project/settings/runners.rb +++ b/qa/qa/page/project/settings/runners.rb @@ -3,14 +3,24 @@ module QA module Project module Settings class Runners < Page::Base + view 'app/views/ci/runner/_how_to_setup_runner.html.haml' do + element :registration_token, '%code#registration_token' + element :coordinator_address, '%code#coordinator_address' + end + + ## + # TODO, phase-out CSS classes from Ruby helpers. + # + view 'app/helpers/runners_helper.rb' do + element :runner_status, 'runner-status-#{status}' + end + def registration_token find('code#registration_token').text end def coordinator_address - # TODO, this needs a specific ID or QA class - # - all('code').first.text + find('code#coordinator_address').text end def has_online_runner? -- cgit v1.2.1 From f64efe603a26f636db4050802afe3d7fd149aa68 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 23 Jan 2018 12:14:29 +0000 Subject: fixed pagination --- app/assets/javascripts/groups/service/groups_service.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/groups/service/groups_service.js b/app/assets/javascripts/groups/service/groups_service.js index 639410384c2..b79ba291463 100644 --- a/app/assets/javascripts/groups/service/groups_service.js +++ b/app/assets/javascripts/groups/service/groups_service.js @@ -1,7 +1,5 @@ import Vue from 'vue'; -import VueResource from 'vue-resource'; - -Vue.use(VueResource); +import '../../vue_shared/vue_resource_interceptor'; export default class GroupsService { constructor(endpoint) { -- cgit v1.2.1 From 55ba4604390242de9728e47673f61703fbbaa02e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 13:53:33 +0100 Subject: Fix static-analysis offenses in QA support class --- qa/qa/scenario/taggable.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qa/qa/scenario/taggable.rb b/qa/qa/scenario/taggable.rb index b63c245bf47..b1f24d742e0 100644 --- a/qa/qa/scenario/taggable.rb +++ b/qa/qa/scenario/taggable.rb @@ -1,6 +1,8 @@ module QA module Scenario module Taggable + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def tags(*tags) @tags = tags end @@ -8,6 +10,8 @@ module QA def focus @tags.to_a end + + # rubocop:enable Gitlab/ModuleWithInstanceVariables end end end -- cgit v1.2.1 From a4e581229e729c5f21fa65cfc708e964b65b6c5b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 13:57:07 +0100 Subject: Wait for runner until it registers itself in QA tests --- qa/qa/specs/features/project/pipelines_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb index 6760605df72..1bb7730e06c 100644 --- a/qa/qa/specs/features/project/pipelines_spec.rb +++ b/qa/qa/specs/features/project/pipelines_spec.rb @@ -15,6 +15,7 @@ module QA end Page::Project::Settings::CICD.perform do |settings| + sleep 5 # Runner should register within 5 seconds settings.refresh settings.expand_runners_settings do |page| -- cgit v1.2.1 From 5bead3e29965ec26ccf3320d65a5757d673d9958 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 23 Jan 2018 14:31:32 +0100 Subject: Fix offense in runners settings QA page object class --- qa/qa/page/project/settings/runners.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb index ff763402d98..b41668c94cd 100644 --- a/qa/qa/page/project/settings/runners.rb +++ b/qa/qa/page/project/settings/runners.rb @@ -9,10 +9,12 @@ module QA end ## - # TODO, phase-out CSS classes from Ruby helpers. + # TODO, phase-out CSS classes added in Ruby helpers. # view 'app/helpers/runners_helper.rb' do + # rubocop:disable Lint/InterpolationCheck element :runner_status, 'runner-status-#{status}' + # rubocop:enable Lint/InterpolationCheck end def registration_token -- cgit v1.2.1 From 8dd359ab788776778c1dedf7be6113704414b5d9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 23 Jan 2018 15:22:32 +0100 Subject: Clarify that a feature that isn't in review by the 1st or 3rd doesn't necessarily miss the freeze --- PROCESS.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 3fcf676b302..99af3be7f14 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -85,7 +85,8 @@ These types of merge requests for the upcoming release need special consideratio and a dedicated team with front-end, back-end, and UX. * **Small features**: any other feature request. -**Large features** must be with a maintainer **by the 1st**. This means that: +It is strongly recommended that **large features** be with a maintainer **by the +1st**. This means that: * There is a merge request (even if it's WIP). * The person (or people, if it needs a frontend and backend maintainer) who will @@ -100,14 +101,37 @@ The maintainer can also choose to assign a reviewer to perform an initial review, but this way the maintainer is unlikely to be surprised by receiving an MR later in the cycle. -**Small features** must be with a reviewer (not necessarily maintainer) **by the -3rd**. +It is strongly recommended that **small features** be with a reviewer (not +necessarily a maintainer) **by the 3rd**. Most merge requests from the community do not have a specific release target. However, if one does and falls into either of the above categories, it's the reviewer's responsibility to manage the above communication and assignment on behalf of the community member. +#### What happens if these deadlines are missed? + +If a small or large feature is _not_ with a maintainer or reviewer by the +recommended date, this does _not_ mean that maintainers or reviewers will refuse +to review or merge it, or that the feature will definitely not make it in before +the feature freeze. + +However, with every day that passes without review, it will become more likely +that the feature will slip, because maintainers and reviewers may not have +enough time to do a thorough review, and developers may not have enough time to +adequately address any feedback that may come back. + +A maintainer or reviewer may also determine that it will not be possible to +finish the current scope of the feature in time, but that it is possible to +reduce the scope so that something can still ship this month, with the remaining +scope moving to the next release. The sooner this decision is made, in +conversation with the Product Manager and developer, the more time there is to +extract that which is now out of scope, and to finish that which remains in scope. + +For these reasons, it is strongly recommended to follow the guidelines above, +to maximize the chances of your feature making it in before the feature freeze, +and to prevent any last minute surprises. + ### On the 7th Merge requests should still be complete, following the -- cgit v1.2.1 From fca2a75266cfeefb2497a0a43b7bca127c970597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 22 Jan 2018 11:13:33 +0100 Subject: Ensure Gitaly Ruby gems are installed using the correct Gemfile and at the correct location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 2 +- scripts/gitaly-test-build | 16 +++++++++++++--- scripts/prepare_build.sh | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f9c5ebe7a35..bb1c84ab918 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git - gitlab-org .default-cache: &default-cache - key: "ruby-235-with-yarn" + key: "ruby-2.3.6-with-yarn" paths: - vendor/ruby - .yarn-cache/ diff --git a/scripts/gitaly-test-build b/scripts/gitaly-test-build index 95d9fe0f176..b42ae2a2595 100755 --- a/scripts/gitaly-test-build +++ b/scripts/gitaly-test-build @@ -9,11 +9,21 @@ require 'fileutils' # called 'bundle install' using a different Gemfile, as happens with # gitlab-ce and gitaly. -dir = 'tmp/tests/gitaly' +tmp_tests_gitaly_dir = File.expand_path('../tmp/tests/gitaly', __dir__) -abort 'gitaly build failed' unless system('make', chdir: dir) +# Use the top-level bundle vendor folder so that we don't reinstall gems twice +bundle_vendor_path = File.expand_path('../vendor', __dir__) + +env = { + # This ensure the `clean` config set in `scripts/prepare_build.sh` isn't taken into account + 'BUNDLE_IGNORE_CONFIG' => 'true', + 'BUNDLE_GEMFILE' => File.join(tmp_tests_gitaly_dir, 'ruby', 'Gemfile'), + 'BUNDLE_FLAGS' => "--jobs=4 --path=#{bundle_vendor_path} --retry=3" +} + +abort 'gitaly build failed' unless system(env, 'make', chdir: tmp_tests_gitaly_dir) # Make the 'gitaly' executable look newer than 'GITALY_SERVER_VERSION'. # Without this a gitaly executable created in the setup-test-env job # will look stale compared to GITALY_SERVER_VERSION. -FileUtils.touch(File.join(dir, 'gitaly'), mtime: Time.now + (1 << 24)) +FileUtils.touch(File.join(tmp_tests_gitaly_dir, 'gitaly'), mtime: Time.now + (1 << 24)) diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index ea406aadf39..206d62dbc78 100644 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -3,7 +3,7 @@ export SETUP_DB=${SETUP_DB:-true} export CREATE_DB_USER=${CREATE_DB_USER:-$SETUP_DB} export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true} -export BUNDLE_INSTALL_FLAGS="--without production --jobs $(nproc) --path vendor --retry 3 --quiet" +export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet" if [ "$USE_BUNDLE_INSTALL" != "false" ]; then bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check -- cgit v1.2.1 From 81bdb6ad8a39b7b9645bcfb4178a14a11f5de9e1 Mon Sep 17 00:00:00 2001 From: Tim Zallmann Date: Tue, 23 Jan 2018 15:19:56 +0000 Subject: Upgrade jasmine + raven to newer versions --- app/assets/javascripts/fly_out_nav.js | 4 +-- package.json | 4 +-- spec/javascripts/fly_out_nav_spec.js | 32 ++++++++++------------ spec/javascripts/helpers/class_spec_helper_spec.js | 20 +------------- .../vue_shared/components/modal_spec.js | 2 +- .../sidebar/collapsed_grouped_date_picker_spec.js | 9 +++--- yarn.lock | 16 +++++------ 7 files changed, 32 insertions(+), 55 deletions(-) diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index abb04d77f8f..8b4f3b05ee7 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -118,14 +118,14 @@ export const showSubLevelItems = (el) => { moveSubItemsToPosition(el, subItems); }; -export const mouseEnterTopItems = (el) => { +export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { if (currentOpenMenu) hideMenu(currentOpenMenu); showSubLevelItems(el); - }, getHideSubItemsInterval()); + }, timeout); }; export const mouseLeaveTopItem = (el) => { diff --git a/package.json b/package.json index eb502ede62a..ef58f71ef8b 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "pikaday": "^1.6.1", "prismjs": "^1.6.0", "raphael": "^2.2.7", - "raven-js": "^3.14.0", + "raven-js": "^3.22.1", "raw-loader": "^0.5.1", "react-dev-utils": "^0.5.2", "sanitize-html": "^1.16.1", @@ -100,7 +100,7 @@ "eslint-plugin-promise": "^3.5.0", "eslint-plugin-vue": "^4.0.1", "istanbul": "^0.4.5", - "jasmine-core": "^2.6.3", + "jasmine-core": "^2.9.0", "jasmine-jquery": "^2.1.1", "karma": "^2.0.0", "karma-chrome-launcher": "^2.2.0", diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js index a3fa07d5bc2..eb9330f5e5b 100644 --- a/spec/javascripts/fly_out_nav_spec.js +++ b/spec/javascripts/fly_out_nav_spec.js @@ -167,30 +167,26 @@ describe('Fly out sidebar navigation', () => { describe('mouseEnterTopItems', () => { beforeEach(() => { - jasmine.clock().install(); - el.innerHTML = ''; }); - afterEach(() => { - jasmine.clock().uninstall(); - }); - - it('shows sub-items after 0ms if no menu is open', () => { + it('shows sub-items after 0ms if no menu is open', (done) => { mouseEnterTopItems(el); expect( getHideSubItemsInterval(), ).toBe(0); - jasmine.clock().tick(0); + setTimeout(() => { + expect( + el.querySelector('.sidebar-sub-level-items').style.display, + ).toBe('block'); - expect( - el.querySelector('.sidebar-sub-level-items').style.display, - ).toBe('block'); + done(); + }); }); - it('shows sub-items after 300ms if a menu is currently open', () => { + it('shows sub-items after 300ms if a menu is currently open', (done) => { documentMouseMove({ clientX: el.getBoundingClientRect().left, clientY: el.getBoundingClientRect().top, @@ -203,17 +199,19 @@ describe('Fly out sidebar navigation', () => { clientY: el.getBoundingClientRect().top + 10, }); - mouseEnterTopItems(el); + mouseEnterTopItems(el, 0); expect( getHideSubItemsInterval(), ).toBe(300); - jasmine.clock().tick(300); + setTimeout(() => { + expect( + el.querySelector('.sidebar-sub-level-items').style.display, + ).toBe('block'); - expect( - el.querySelector('.sidebar-sub-level-items').style.display, - ).toBe('block'); + done(); + }); }); }); diff --git a/spec/javascripts/helpers/class_spec_helper_spec.js b/spec/javascripts/helpers/class_spec_helper_spec.js index 686b8eaed31..1415ffb7eb3 100644 --- a/spec/javascripts/helpers/class_spec_helper_spec.js +++ b/spec/javascripts/helpers/class_spec_helper_spec.js @@ -3,7 +3,7 @@ import './class_spec_helper'; describe('ClassSpecHelper', () => { - describe('itShouldBeAStaticMethod', function () { + describe('itShouldBeAStaticMethod', () => { beforeEach(() => { class TestClass { instanceMethod() { this.prop = 'val'; } @@ -14,23 +14,5 @@ describe('ClassSpecHelper', () => { }); ClassSpecHelper.itShouldBeAStaticMethod(ClassSpecHelper, 'itShouldBeAStaticMethod'); - - it('should have a defined spec', () => { - expect(ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod').description).toBe('should be a static method'); - }); - - it('should pass for a static method', () => { - const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod'); - expect(spec.status()).toBe('passed'); - }); - - it('should fail for an instance method', (done) => { - const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'instanceMethod'); - spec.resultCallback = (result) => { - expect(result.status).toBe('failed'); - done(); - }; - spec.execute(); - }); }); }); diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js index fe75a86cac8..a5f9c75be4e 100644 --- a/spec/javascripts/vue_shared/components/modal_spec.js +++ b/spec/javascripts/vue_shared/components/modal_spec.js @@ -25,7 +25,7 @@ describe('Modal', () => { }); describe('with id', () => { - it('does not render a primary button', () => { + describe('does not render a primary button', () => { beforeEach(() => { vm = mountComponent(modalComponent, { id: 'my-modal', diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js index 20363e78094..2de108da2ac 100644 --- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js @@ -21,22 +21,21 @@ describe('collapsedGroupedDatePicker', () => { }); }); - it('toggleCollapse events', () => { - const toggleCollapse = jasmine.createSpy(); - + describe('toggleCollapse events', () => { beforeEach((done) => { + spyOn(vm, 'toggleSidebar'); vm.minDate = new Date('07/17/2016'); Vue.nextTick(done); }); it('should emit when sidebar is toggled', () => { vm.$el.querySelector('.gutter-toggle').click(); - expect(toggleCollapse).toHaveBeenCalled(); + expect(vm.toggleSidebar).toHaveBeenCalled(); }); it('should emit when collapsed-calendar-icon is clicked', () => { vm.$el.querySelector('.sidebar-collapsed-icon').click(); - expect(toggleCollapse).toHaveBeenCalled(); + expect(vm.toggleSidebar).toHaveBeenCalled(); }); }); diff --git a/yarn.lock b/yarn.lock index c3345db9c83..e66affe2bd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4185,9 +4185,9 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" -jasmine-core@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.3.tgz#45072950e4a42b1e322fe55c001100a465d77815" +jasmine-core@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.9.0.tgz#bfbb56defcd30789adec5a3fbba8504233289c72" jasmine-jquery@^2.1.1: version "2.1.1" @@ -4269,7 +4269,7 @@ json-stable-stringify@~0.0.0: dependencies: jsonify "~0.0.0" -json-stringify-safe@5.0.x, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@5.0.x, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -6067,11 +6067,9 @@ raphael@^2.2.7: dependencies: eve-raphael "0.5.0" -raven-js@^3.14.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.14.0.tgz#94dda81d975fdc4a42f193db437cf70021d654e0" - dependencies: - json-stringify-safe "^5.0.1" +raven-js@^3.22.1: + version "3.22.1" + resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.22.1.tgz#1117f00dfefaa427ef6e1a7d50bbb1fb998a24da" raw-body@2: version "2.3.2" -- cgit v1.2.1 From da3faca7be258c25a53d648a49ae16b7e4b8171c Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 21 Jan 2018 15:16:25 +0900 Subject: Set timezone for karma to UTC --- changelogs/unreleased/42251-explicit-timezone-for-karma.yml | 5 +++++ config/karma.config.js | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 changelogs/unreleased/42251-explicit-timezone-for-karma.yml diff --git a/changelogs/unreleased/42251-explicit-timezone-for-karma.yml b/changelogs/unreleased/42251-explicit-timezone-for-karma.yml new file mode 100644 index 00000000000..25e0e774c48 --- /dev/null +++ b/changelogs/unreleased/42251-explicit-timezone-for-karma.yml @@ -0,0 +1,5 @@ +--- +title: Set timezone for karma to UTC +merge_request: 16602 +author: Takuya Noguchi +type: other diff --git a/config/karma.config.js b/config/karma.config.js index 9f018d14b8f..a101d35704e 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -18,6 +18,8 @@ webpackConfig.devtool = 'cheap-inline-source-map'; // Karma configuration module.exports = function(config) { + process.env.TZ = 'Etc/UTC'; + var progressReporter = process.env.CI ? 'mocha' : 'progress'; var karmaConfig = { -- cgit v1.2.1 From 0b47c94a7243e956221fe2447e5c338d8ff2280b Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Tue, 23 Jan 2018 16:00:15 +0000 Subject: remove webpack bundle tag for monitoring:environments:metrics --- app/assets/javascripts/dispatcher.js | 5 +++++ app/assets/javascripts/monitoring/monitoring_bundle.js | 4 ++-- app/assets/javascripts/pages/projects/environments/metrics/index.js | 3 +++ app/views/projects/environments/metrics.html.haml | 1 - 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/environments/metrics/index.js diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 96e5c8c890c..895251cec3c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -68,6 +68,11 @@ import SearchAutocomplete from './search_autocomplete'; .catch(fail); shortcut_handler = true; break; + case 'projects:environments:metrics': + import('./pages/projects/environments/metrics') + .then(callDefault) + .catch(fail); + break; case 'projects:merge_requests:index': import('./pages/projects/merge_requests/index') .then(callDefault) diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index 104432ef5de..c3b0ef7e9ca 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import Dashboard from './components/dashboard.vue'; -document.addEventListener('DOMContentLoaded', () => new Vue({ +export default () => new Vue({ el: '#prometheus-graphs', render: createElement => createElement(Dashboard), -})); +}); diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js new file mode 100644 index 00000000000..f4760cb2720 --- /dev/null +++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js @@ -0,0 +1,3 @@ +import monitoringBundle from '~/monitoring/monitoring_bundle'; + +export default monitoringBundle; diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index ad94113fffd..5257b42548e 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -3,7 +3,6 @@ - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_d3' - = webpack_bundle_tag 'monitoring' .prometheus-container{ class: container_class } .top-area -- cgit v1.2.1 From 4ca358891d1906b30af52c4443dc9c5a0f173947 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 23 Jan 2018 17:12:44 +0100 Subject: Backport changes to Gitlab::Checks::ChangeAccess from EE --- lib/gitlab/checks/change_access.rb | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index ef92fc5a0a0..945d70e7a24 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -16,7 +16,7 @@ module Gitlab lfs_objects_missing: 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".' }.freeze - attr_reader :user_access, :project, :skip_authorization, :protocol + attr_reader :user_access, :project, :skip_authorization, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name def initialize( change, user_access:, project:, skip_authorization: false, @@ -51,9 +51,9 @@ module Gitlab end def branch_checks - return unless @branch_name + return unless branch_name - if deletion? && @branch_name == project.default_branch + if deletion? && branch_name == project.default_branch raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch] end @@ -61,7 +61,7 @@ module Gitlab end def protected_branch_checks - return unless ProtectedBranch.protected?(project, @branch_name) + return unless ProtectedBranch.protected?(project, branch_name) if forced_push? raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch] @@ -75,29 +75,29 @@ module Gitlab end def protected_branch_deletion_checks - unless user_access.can_delete_branch?(@branch_name) + unless user_access.can_delete_branch?(branch_name) raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch] end - unless protocol == 'web' + unless updated_from_web? raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch] end end def protected_branch_push_checks if matching_merge_request? - unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name) + unless user_access.can_merge_to_branch?(branch_name) || user_access.can_push_to_branch?(branch_name) raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch] end else - unless user_access.can_push_to_branch?(@branch_name) + unless user_access.can_push_to_branch?(branch_name) raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_protected_branch] end end end def tag_checks - return unless @tag_name + return unless tag_name if tag_exists? && user_access.cannot_do_action?(:admin_project) raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags] @@ -107,40 +107,44 @@ module Gitlab end def protected_tag_checks - return unless ProtectedTag.protected?(project, @tag_name) + return unless ProtectedTag.protected?(project, tag_name) raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update? raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion? - unless user_access.can_create_tag?(@tag_name) + unless user_access.can_create_tag?(tag_name) raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag] end end private + def updated_from_web? + protocol == 'web' + end + def tag_exists? - project.repository.tag_exists?(@tag_name) + project.repository.tag_exists?(tag_name) end def forced_push? - Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev) + Gitlab::Checks::ForcePush.force_push?(project, oldrev, newrev) end def update? - !Gitlab::Git.blank_ref?(@oldrev) && !deletion? + !Gitlab::Git.blank_ref?(oldrev) && !deletion? end def deletion? - Gitlab::Git.blank_ref?(@newrev) + Gitlab::Git.blank_ref?(newrev) end def matching_merge_request? - Checks::MatchingMergeRequest.new(@newrev, @branch_name, @project).match? + Checks::MatchingMergeRequest.new(newrev, branch_name, project).match? end def lfs_objects_exist_check - lfs_check = Checks::LfsIntegrity.new(project, @newrev) + lfs_check = Checks::LfsIntegrity.new(project, newrev) if lfs_check.objects_missing? raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:lfs_objects_missing] -- cgit v1.2.1 From d8eb961d0b84ef35e1b856869f5ae2a880c4b84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 23 Jan 2018 17:42:37 +0100 Subject: Fix a migration spec messing up the MergeRequestDiff DB schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../populate_merge_request_metrics_with_events_data_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb index e99257e3481..4cdb679c97f 100644 --- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb @@ -7,6 +7,10 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, .to receive(:commits_count=).and_return(nil) end + after do + [MergeRequest, MergeRequestDiff].each(&:reset_column_information) + end + describe '#perform' do let(:mr_with_event) { create(:merge_request) } let!(:merged_event) { create(:event, :merged, target: mr_with_event) } -- cgit v1.2.1 From dc84313e73da8454cd043dbd235a2b552658d8ae Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 23 Jan 2018 19:08:10 -0200 Subject: Return more consistent values for merge_status on MR API --- app/models/merge_request.rb | 7 +++++++ .../osw-updates-merge-status-on-api-actions.yml | 5 +++++ lib/api/entities.rb | 11 ++++++++++- spec/models/merge_request_spec.rb | 17 +++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 8028ff3875b..4addf42325d 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -56,6 +56,7 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :clear_memoized_shas after_update :reload_diff_if_branch_changed + after_update :mark_as_unchecked_if_target_branch_changed # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -561,6 +562,12 @@ class MergeRequest < ActiveRecord::Base end end + def mark_as_unchecked_if_target_branch_changed + return unless target_branch_changed? + + mark_as_unchecked + end + def reload_diff(current_user = nil) return unless open? diff --git a/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml b/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml new file mode 100644 index 00000000000..cb3b1590acf --- /dev/null +++ b/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml @@ -0,0 +1,5 @@ +--- +title: Update merge_status on MR creation/update/get on the API +merge_request: +author: +type: fixed diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3f4b62dc1b2..6af379451a6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -507,7 +507,16 @@ module API expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone expose :merge_when_pipeline_succeeds - expose :merge_status + + # Ideally we should deprecate `MergeRequest#merge_status` exposure and + # use `MergeRequest#mergeable?` instead (boolean). + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more + # information. + expose :merge_status do |merge_request| + # In order to avoid having a breaking change for users, we keep returning the + # expected values from MergeRequest#merge_status state machine. + merge_request.mergeable? ? 'can_be_merged' : 'cannot_be_merged' + end expose :diff_head_sha, as: :sha expose :merge_commit_sha expose :user_notes_count diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c76f32b3989..7cc237937f3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -77,6 +77,23 @@ describe MergeRequest do expect(MergeRequest::Metrics.count).to eq(1) end end + + describe '#mark_as_unchecked_if_target_branch_changed' do + let(:merge_request) { create(:merge_request, merge_status: :can_be_merged) } + + it 'marks MR as unchecked if target_branch changes' do + expect { merge_request.update!(target_branch: 'bar') } + .to change(merge_request, :merge_status) + .from('can_be_merged') + .to('unchecked') + end + + it 'does not marks MR as unchecked when target_branch does not changes' do + expect { merge_request.update!(title: 'foo') } + .not_to change(merge_request, :merge_status) + .from('can_be_merged') + end + end end describe 'respond to' do -- cgit v1.2.1 From 0b965528c6dd6db67adf5b86c62c19088ebe65f5 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Tue, 23 Jan 2018 18:31:56 +0100 Subject: GitalyClient::ConflictsService#conflicts? should return true for conflicts with missing side --- lib/gitlab/gitaly_client/conflicts_service.rb | 5 +++++ spec/models/repository_spec.rb | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb index 2565d537aff..e14734495a8 100644 --- a/lib/gitlab/gitaly_client/conflicts_service.rb +++ b/lib/gitlab/gitaly_client/conflicts_service.rb @@ -25,6 +25,11 @@ module Gitlab def conflicts? list_conflict_files.any? + rescue GRPC::FailedPrecondition + # The server raises this exception when it encounters ConflictSideMissing, which + # means a conflict exists but its `theirs` or `ours` data is nil due to a non-existent + # file in one of the trees. + true end def resolve_conflicts(target_repository, resolution, source_branch, target_branch) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index baaa9e3ef44..8f406253f39 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -365,12 +365,18 @@ describe Repository do it { is_expected.to be_truthy } end - context 'non-mergeable branches' do + context 'non-mergeable branches without conflict sides missing' do subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') } it { is_expected.to be_falsey } end + context 'non-mergeable branches with conflict sides missing' do + subject { repository.can_be_merged?('conflict-missing-side', 'conflict-start') } + + it { is_expected.to be_falsey } + end + context 'non merged branch' do subject { repository.merged_to_root_ref?('fix') } -- cgit v1.2.1 From c5893d5c83301901522bc354e8b88600c2e083f3 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Tue, 23 Jan 2018 19:30:29 -0200 Subject: Update changelog --- changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml b/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml index cb3b1590acf..3854985e576 100644 --- a/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml +++ b/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml @@ -1,5 +1,5 @@ --- -title: Update merge_status on MR creation/update/get on the API +title: Return more consistent values for merge_status on MR APIs merge_request: author: type: fixed -- cgit v1.2.1 From 090ca9c33e4c1939366e66c328af6dd61bf1db1d Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 23 Jan 2018 12:03:15 +0100 Subject: Use limit for search count queries Search query is especially slow if a user searches a generic string which matches many records, in such case search can take tens of seconds or time out. To speed up the search query, we search only for first 1000 records, if there is >1000 matching records we just display "1000+" instead of precise total count supposing that with such amount the exact count is not so important for the user. Because for issues even limited search was not fast enough, 2-phase approach is used for issues: first we use simpler/faster query to get all public issues, if this exceeds the limit, we just return the limit. If the amount of matching results is lower than limit, we re-run more complex search query (which includes also confidential issues). Re-running the complex query should be fast enough in such case because the amount of matching issues is lower than limit. Because exact total_count is now limited, this patch also switches to to "prev/next" pagination. Related #40540 --- app/finders/issues_finder.rb | 11 +++- app/helpers/search_helper.rb | 4 ++ app/views/search/_category.html.haml | 9 ++- app/views/search/_results.html.haml | 5 +- .../40540-use-limit-for-global-search.yml | 5 ++ ...0180115201419_add_index_updated_at_to_issues.rb | 15 +++++ db/schema.rb | 3 +- lib/api/v3/projects.rb | 2 +- lib/gitlab/project_search_results.rb | 2 +- lib/gitlab/search_results.rb | 66 ++++++++++++++++------ lib/gitlab/snippet_search_results.rb | 2 +- spec/features/global_search_spec.rb | 2 +- spec/lib/gitlab/search_results_spec.rb | 58 +++++++++++++++++++ 13 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 changelogs/unreleased/40540-use-limit-for-global-search.yml create mode 100644 db/migrate/20180115201419_add_index_updated_at_to_issues.rb diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index d2275139c42..98831f5be4a 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -15,6 +15,7 @@ # label_name: string # sort: string # my_reaction_emoji: string +# public_only: boolean # class IssuesFinder < IssuableFinder CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER @@ -40,7 +41,15 @@ class IssuesFinder < IssuableFinder private def init_collection - with_confidentiality_access_check + if public_only? + Issue.public_only + else + with_confidentiality_access_check + end + end + + def public_only? + params.fetch(:public_only, false) end def user_can_see_all_confidential_issues? diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 0f9ac958f95..e6a6496871a 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -170,4 +170,8 @@ module SearchHelper # Truncato's filtered_tags and filtered_attributes are not quite the same sanitize(html, tags: %w(a p ol ul li pre code)) end + + def limited_count(count, limit = 1000) + count > limit ? "#{limit}+" : count + end end diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml index 314d8e9cb25..915e648a5d3 100644 --- a/app/views/search/_category.html.haml +++ b/app/views/search/_category.html.haml @@ -57,25 +57,24 @@ Titles and Filenames %span.badge = @search_results.snippet_titles_count - - else %li{ class: active_when(@scope == 'projects') } = link_to search_filter_path(scope: 'projects') do Projects %span.badge - = @search_results.projects_count + = limited_count(@search_results.limited_projects_count) %li{ class: active_when(@scope == 'issues') } = link_to search_filter_path(scope: 'issues') do Issues %span.badge - = @search_results.issues_count + = limited_count(@search_results.limited_issues_count) %li{ class: active_when(@scope == 'merge_requests') } = link_to search_filter_path(scope: 'merge_requests') do Merge requests %span.badge - = @search_results.merge_requests_count + = limited_count(@search_results.limited_merge_requests_count) %li{ class: active_when(@scope == 'milestones') } = link_to search_filter_path(scope: 'milestones') do Milestones %span.badge - = @search_results.milestones_count + = limited_count(@search_results.limited_milestones_count) diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 02133d09cdf..60ef44482f0 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -2,7 +2,8 @@ = render partial: "search/results/empty" - else .row-content-block - = search_entries_info(@search_objects, @scope, @search_term) + - unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount) + = search_entries_info(@search_objects, @scope, @search_term) - unless @show_snippets - if @project in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]} @@ -22,4 +23,4 @@ = render partial: "search/results/#{@scope.singularize}", collection: @search_objects - if @scope != 'projects' - = paginate(@search_objects, theme: 'gitlab') + = paginate_collection(@search_objects) diff --git a/changelogs/unreleased/40540-use-limit-for-global-search.yml b/changelogs/unreleased/40540-use-limit-for-global-search.yml new file mode 100644 index 00000000000..7d9612c16df --- /dev/null +++ b/changelogs/unreleased/40540-use-limit-for-global-search.yml @@ -0,0 +1,5 @@ +--- +title: Optimize search queries on the search page by setting a limit for matching records. +merge_request: +author: +type: performance diff --git a/db/migrate/20180115201419_add_index_updated_at_to_issues.rb b/db/migrate/20180115201419_add_index_updated_at_to_issues.rb new file mode 100644 index 00000000000..a5a48fc97be --- /dev/null +++ b/db/migrate/20180115201419_add_index_updated_at_to_issues.rb @@ -0,0 +1,15 @@ +class AddIndexUpdatedAtToIssues < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :issues, :updated_at + end + + def down + remove_concurrent_index :issues, :updated_at + end +end diff --git a/db/schema.rb b/db/schema.rb index a0901833c3d..4e82a688725 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180113220114) do +ActiveRecord::Schema.define(version: 20180115201419) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -886,6 +886,7 @@ ActiveRecord::Schema.define(version: 20180113220114) do add_index "issues", ["relative_position"], name: "index_issues_on_relative_position", using: :btree add_index "issues", ["state"], name: "index_issues_on_state", using: :btree add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} + add_index "issues", ["updated_at"], name: "index_issues_on_updated_at", using: :btree add_index "issues", ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree create_table "keys", force: :cascade do |t| diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb index 446f804124b..a7f0813bf74 100644 --- a/lib/api/v3/projects.rb +++ b/lib/api/v3/projects.rb @@ -175,7 +175,7 @@ module API end get "/search/:query", requirements: { query: /[^\/]+/ } do search_service = Search::GlobalService.new(current_user, search: params[:query]).execute - projects = search_service.objects('projects', params[:page]) + projects = search_service.objects('projects', params[:page], false) projects = projects.reorder(params[:order_by] => params[:sort]) present paginate(projects), with: ::API::V3::Entities::Project diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 7771b15069b..4823f703ba4 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -20,7 +20,7 @@ module Gitlab when 'commits' Kaminari.paginate_array(commits).page(page).per(per_page) else - super + super(scope, page, false) end end diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 70b639501fd..7362514167f 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -40,19 +40,21 @@ module Gitlab @default_project_filter = default_project_filter end - def objects(scope, page = nil) - case scope - when 'projects' - projects.page(page).per(per_page) - when 'issues' - issues.page(page).per(per_page) - when 'merge_requests' - merge_requests.page(page).per(per_page) - when 'milestones' - milestones.page(page).per(per_page) - else - Kaminari.paginate_array([]).page(page).per(per_page) - end + def objects(scope, page = nil, without_count = true) + collection = case scope + when 'projects' + projects.page(page).per(per_page) + when 'issues' + issues.page(page).per(per_page) + when 'merge_requests' + merge_requests.page(page).per(per_page) + when 'milestones' + milestones.page(page).per(per_page) + else + Kaminari.paginate_array([]).page(page).per(per_page) + end + + without_count ? collection.without_count : collection end def projects_count @@ -71,18 +73,46 @@ module Gitlab @milestones_count ||= milestones.count end + def limited_projects_count + @limited_projects_count ||= projects.limit(count_limit).count + end + + def limited_issues_count + return @limited_issues_count if @limited_issues_count + + # By default getting limited count (e.g. 1000+) is fast on issuable + # collections except for issues, where filtering both not confidential + # and confidential issues user has access to, is too complex. + # It's faster to try to fetch all public issues first, then only + # if necessary try to fetch all issues. + sum = issues(public_only: true).limit(count_limit).count + @limited_issues_count = sum < count_limit ? issues.limit(count_limit).count : sum + end + + def limited_merge_requests_count + @limited_merge_requests_count ||= merge_requests.limit(count_limit).count + end + + def limited_milestones_count + @limited_milestones_count ||= milestones.limit(count_limit).count + end + def single_commit_result? false end + def count_limit + 1001 + end + private def projects limit_projects.search(query) end - def issues - issues = IssuesFinder.new(current_user).execute + def issues(finder_params = {}) + issues = IssuesFinder.new(current_user, finder_params).execute unless default_project_filter issues = issues.where(project_id: project_ids_relation) end @@ -94,13 +124,13 @@ module Gitlab issues.full_search(query) end - issues.order('updated_at DESC') + issues.reorder('updated_at DESC') end def milestones milestones = Milestone.where(project_id: project_ids_relation) milestones = milestones.search(query) - milestones.order('updated_at DESC') + milestones.reorder('updated_at DESC') end def merge_requests @@ -116,7 +146,7 @@ module Gitlab merge_requests.full_search(query) end - merge_requests.order('updated_at DESC') + merge_requests.reorder('updated_at DESC') end def default_scope diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb index b85f70e450e..4f86b3e8f73 100644 --- a/lib/gitlab/snippet_search_results.rb +++ b/lib/gitlab/snippet_search_results.rb @@ -16,7 +16,7 @@ module Gitlab when 'snippet_blobs' snippet_blobs.page(page).per(per_page) else - super + super(scope, nil, false) end end diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb index 4f575613848..f8c4db1403c 100644 --- a/spec/features/global_search_spec.rb +++ b/spec/features/global_search_spec.rb @@ -22,7 +22,7 @@ feature 'Global search' do click_button "Go" select_filter("Issues") - expect(page).to have_selector('.gl-pagination .page', count: 2) + expect(page).to have_selector('.gl-pagination .next') end end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index b5a9ac570e6..17b48b3d062 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -19,6 +19,12 @@ describe Gitlab::SearchResults do project.add_developer(user) end + describe '#objects' do + it 'returns without_page collection by default' do + expect(results.objects('projects')).to be_kind_of(Kaminari::PaginatableWithoutCount) + end + end + describe '#projects_count' do it 'returns the total amount of projects' do expect(results.projects_count).to eq(1) @@ -43,6 +49,58 @@ describe Gitlab::SearchResults do end end + context "when count_limit is lower than total amount" do + before do + allow(results).to receive(:count_limit).and_return(1) + end + + describe '#limited_projects_count' do + it 'returns the limited amount of projects' do + create(:project, name: 'foo2') + + expect(results.limited_projects_count).to eq(1) + end + end + + describe '#limited_merge_requests_count' do + it 'returns the limited amount of merge requests' do + create(:merge_request, :simple, source_project: project, title: 'foo2') + + expect(results.limited_merge_requests_count).to eq(1) + end + end + + describe '#limited_milestones_count' do + it 'returns the limited amount of milestones' do + create(:milestone, project: project, title: 'foo2') + + expect(results.limited_milestones_count).to eq(1) + end + end + + describe '#limited_issues_count' do + it 'runs single SQL query to get the limited amount of issues' do + create(:milestone, project: project, title: 'foo2') + + expect(results).to receive(:issues).with(public_only: true).and_call_original + expect(results).not_to receive(:issues).with(no_args).and_call_original + + expect(results.limited_issues_count).to eq(1) + end + end + end + + context "when count_limit is higher than total amount" do + describe '#limited_issues_count' do + it 'runs multiple queries to get the limited amount of issues' do + expect(results).to receive(:issues).with(public_only: true).and_call_original + expect(results).to receive(:issues).with(no_args).and_call_original + + expect(results.limited_issues_count).to eq(1) + end + end + end + it 'includes merge requests from source and target projects' do forked_project = fork_project(project, user) merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo') -- cgit v1.2.1 From 0683d31ab162284f9f37004da7365ce2f3e6da55 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 11 Jan 2018 23:12:34 +0000 Subject: Can parse root .gitattributes file for a ref --- .../rugged_use_gitlab_git_attributes.rb | 4 +- lib/gitlab/git/attributes.rb | 136 ------------------ lib/gitlab/git/attributes_at_ref_parser.rb | 14 ++ lib/gitlab/git/attributes_parser.rb | 115 ++++++++++++++++ lib/gitlab/git/info_attributes.rb | 49 +++++++ lib/gitlab/git/repository.rb | 14 +- .../gitlab/git/attributes_at_ref_parser_spec.rb | 28 ++++ spec/lib/gitlab/git/attributes_parser_spec.rb | 153 +++++++++++++++++++++ spec/lib/gitlab/git/attributes_spec.rb | 150 -------------------- spec/lib/gitlab/git/info_attributes_spec.rb | 43 ++++++ spec/support/test_env.rb | 2 +- 11 files changed, 418 insertions(+), 290 deletions(-) delete mode 100644 lib/gitlab/git/attributes.rb create mode 100644 lib/gitlab/git/attributes_at_ref_parser.rb create mode 100644 lib/gitlab/git/attributes_parser.rb create mode 100644 lib/gitlab/git/info_attributes.rb create mode 100644 spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb create mode 100644 spec/lib/gitlab/git/attributes_parser_spec.rb delete mode 100644 spec/lib/gitlab/git/attributes_spec.rb create mode 100644 spec/lib/gitlab/git/info_attributes_spec.rb diff --git a/config/initializers/rugged_use_gitlab_git_attributes.rb b/config/initializers/rugged_use_gitlab_git_attributes.rb index 1cfb3bcb4bd..c0d45caec42 100644 --- a/config/initializers/rugged_use_gitlab_git_attributes.rb +++ b/config/initializers/rugged_use_gitlab_git_attributes.rb @@ -7,7 +7,7 @@ # repository-wide language statistics: # # -# The options passed by Linguist are those assumed by Gitlab::Git::Attributes +# The options passed by Linguist are those assumed by Gitlab::Git::InfoAttributes # anyway, and there is no great efficiency gain from just fetching the listed # attributes with our implementation, so we ignore the additional arguments. # @@ -19,7 +19,7 @@ module Rugged end def attributes - @attributes ||= Gitlab::Git::Attributes.new(path) + @attributes ||= Gitlab::Git::InfoAttributes.new(path) end end diff --git a/lib/gitlab/git/attributes.rb b/lib/gitlab/git/attributes.rb deleted file mode 100644 index 2d20cd473a7..00000000000 --- a/lib/gitlab/git/attributes.rb +++ /dev/null @@ -1,136 +0,0 @@ -# Gitaly note: JV: not sure what to make of this class. Why does it use -# the full disk path of the repository to look up attributes This is -# problematic in Gitaly, because Gitaly hides the full disk path to the -# repository from gitlab-ce. - -module Gitlab - module Git - # Class for parsing Git attribute files and extracting the attributes for - # file patterns. - # - # Unlike Rugged this parser only needs a single IO call (a call to `open`), - # vastly reducing the time spent in extracting attributes. - # - # This class _only_ supports parsing the attributes file located at - # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files - # (`.gitattributes` is copied to this particular path). - # - # Basic usage: - # - # attributes = Gitlab::Git::Attributes.new(some_repo.path) - # - # attributes.attributes('README.md') # => { "eol" => "lf } - class Attributes - # path - The path to the Git repository. - def initialize(path) - @path = File.expand_path(path) - @patterns = nil - end - - # Returns all the Git attributes for the given path. - # - # path - A path to a file for which to get the attributes. - # - # Returns a Hash. - def attributes(path) - full_path = File.join(@path, path) - - patterns.each do |pattern, attrs| - return attrs if File.fnmatch?(pattern, full_path) - end - - {} - end - - # Returns a Hash containing the file patterns and their attributes. - def patterns - @patterns ||= parse_file - end - - # Parses an attribute string. - # - # These strings can be in the following formats: - # - # text # => { "text" => true } - # -text # => { "text" => false } - # key=value # => { "key" => "value" } - # - # string - The string to parse. - # - # Returns a Hash containing the attributes and their values. - def parse_attributes(string) - values = {} - dash = '-' - equal = '=' - binary = 'binary' - - string.split(/\s+/).each do |chunk| - # Data such as "foo = bar" should be treated as "foo" and "bar" being - # separate boolean attributes. - next if chunk == equal - - key = chunk - - # Input: "-foo" - if chunk.start_with?(dash) - key = chunk.byteslice(1, chunk.length - 1) - value = false - - # Input: "foo=bar" - elsif chunk.include?(equal) - key, value = chunk.split(equal, 2) - - # Input: "foo" - else - value = true - end - - values[key] = value - - # When the "binary" option is set the "diff" option should be set to - # the inverse. If "diff" is later set it should overwrite the - # automatically set value. - values['diff'] = false if key == binary && value - end - - values - end - - # Iterates over every line in the attributes file. - def each_line - full_path = File.join(@path, 'info/attributes') - - return unless File.exist?(full_path) - - File.open(full_path, 'r') do |handle| - handle.each_line do |line| - break unless line.valid_encoding? - - yield line.strip - end - end - end - - private - - # Parses the Git attributes file. - def parse_file - pairs = [] - comment = '#' - - each_line do |line| - next if line.start_with?(comment) || line.empty? - - pattern, attrs = line.split(/\s+/, 2) - - parsed = attrs ? parse_attributes(attrs) : {} - - pairs << [File.join(@path, pattern), parsed] - end - - # Newer entries take precedence over older entries. - pairs.reverse.to_h - end - end - end -end diff --git a/lib/gitlab/git/attributes_at_ref_parser.rb b/lib/gitlab/git/attributes_at_ref_parser.rb new file mode 100644 index 00000000000..26b5bd520d5 --- /dev/null +++ b/lib/gitlab/git/attributes_at_ref_parser.rb @@ -0,0 +1,14 @@ +module Gitlab + module Git + # Parses root .gitattributes file at a given ref + class AttributesAtRefParser + delegate :attributes, to: :@parser + + def initialize(repository, ref) + blob = repository.blob_at(ref, '.gitattributes') + + @parser = AttributesParser.new(blob&.data) + end + end + end +end diff --git a/lib/gitlab/git/attributes_parser.rb b/lib/gitlab/git/attributes_parser.rb new file mode 100644 index 00000000000..d8aeabb6cba --- /dev/null +++ b/lib/gitlab/git/attributes_parser.rb @@ -0,0 +1,115 @@ +module Gitlab + module Git + # Class for parsing Git attribute files and extracting the attributes for + # file patterns. + class AttributesParser + def initialize(attributes_data) + @data = attributes_data || "" + + if @data.is_a?(File) + @patterns = parse_file + end + end + + # Returns all the Git attributes for the given path. + # + # file_path - A path to a file for which to get the attributes. + # + # Returns a Hash. + def attributes(file_path) + absolute_path = File.join('/', file_path) + + patterns.each do |pattern, attrs| + return attrs if File.fnmatch?(pattern, absolute_path) + end + + {} + end + + # Returns a Hash containing the file patterns and their attributes. + def patterns + @patterns ||= parse_file + end + + # Parses an attribute string. + # + # These strings can be in the following formats: + # + # text # => { "text" => true } + # -text # => { "text" => false } + # key=value # => { "key" => "value" } + # + # string - The string to parse. + # + # Returns a Hash containing the attributes and their values. + def parse_attributes(string) + values = {} + dash = '-' + equal = '=' + binary = 'binary' + + string.split(/\s+/).each do |chunk| + # Data such as "foo = bar" should be treated as "foo" and "bar" being + # separate boolean attributes. + next if chunk == equal + + key = chunk + + # Input: "-foo" + if chunk.start_with?(dash) + key = chunk.byteslice(1, chunk.length - 1) + value = false + + # Input: "foo=bar" + elsif chunk.include?(equal) + key, value = chunk.split(equal, 2) + + # Input: "foo" + else + value = true + end + + values[key] = value + + # When the "binary" option is set the "diff" option should be set to + # the inverse. If "diff" is later set it should overwrite the + # automatically set value. + values['diff'] = false if key == binary && value + end + + values + end + + # Iterates over every line in the attributes file. + def each_line + @data.each_line do |line| + break unless line.valid_encoding? + + yield line.strip + end + end + + private + + # Parses the Git attributes file. + def parse_file + pairs = [] + comment = '#' + + each_line do |line| + next if line.start_with?(comment) || line.empty? + + pattern, attrs = line.split(/\s+/, 2) + + parsed = attrs ? parse_attributes(attrs) : {} + + absolute_pattern = File.join('/', pattern) + pairs << [absolute_pattern, parsed] + end + + # Newer entries take precedence over older entries. + pairs.reverse.to_h + end + end + end +end diff --git a/lib/gitlab/git/info_attributes.rb b/lib/gitlab/git/info_attributes.rb new file mode 100644 index 00000000000..e79a440950b --- /dev/null +++ b/lib/gitlab/git/info_attributes.rb @@ -0,0 +1,49 @@ +# Gitaly note: JV: not sure what to make of this class. Why does it use +# the full disk path of the repository to look up attributes This is +# problematic in Gitaly, because Gitaly hides the full disk path to the +# repository from gitlab-ce. + +module Gitlab + module Git + # Parses gitattributes at `$GIT_DIR/info/attributes` + # + # Unlike Rugged this parser only needs a single IO call (a call to `open`), + # vastly reducing the time spent in extracting attributes. + # + # This class _only_ supports parsing the attributes file located at + # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files + # (`.gitattributes` is copied to this particular path). + # + # Basic usage: + # + # attributes = Gitlab::Git::InfoAttributes.new(some_repo.path) + # + # attributes.attributes('README.md') # => { "eol" => "lf } + class InfoAttributes + delegate :attributes, :patterns, to: :parser + + # path - The path to the Git repository. + def initialize(path) + @repo_path = File.expand_path(path) + end + + def parser + @parser ||= begin + if File.exist?(attributes_path) + File.open(attributes_path, 'r') do |file_handle| + AttributesParser.new(file_handle) + end + else + AttributesParser.new("") + end + end + end + + private + + def attributes_path + @attributes_path ||= File.join(@repo_path, 'info/attributes') + end + end + end +end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 283134e043e..736aaf0a642 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -102,7 +102,7 @@ module Gitlab ) @path = File.join(storage_path, @relative_path) @name = @relative_path.split("/").last - @attributes = Gitlab::Git::Attributes.new(path) + @attributes = Gitlab::Git::InfoAttributes.new(path) end def ==(other) @@ -1011,6 +1011,18 @@ module Gitlab attributes(path)[name] end + # Check .gitattributes for a given ref + # + # This only checks the root .gitattributes file, + # it does not traverse subfolders to find additional .gitattributes files + # + # This method is around 30 times slower than `attributes`, + # which uses `$GIT_DIR/info/attributes` + def attributes_at(ref, file_path) + parser = AttributesAtRefParser.new(self, ref) + parser.attributes(file_path) + end + def languages(ref = nil) Gitlab::GitalyClient.migrate(:commit_languages) do |is_enabled| if is_enabled diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb new file mode 100644 index 00000000000..5d22dcfb508 --- /dev/null +++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Gitlab::Git::AttributesAtRefParser, seed_helper: true do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + + subject { described_class.new(repository, 'lfs') } + + it 'loads .gitattributes blob' do + repository.raw # Initialize repository in advance since this also checks attributes + + expected_filter = 'filter=lfs diff=lfs merge=lfs' + receive_blob = receive(:new).with(a_string_including(expected_filter)) + expect(Gitlab::Git::AttributesParser).to receive_blob.and_call_original + + subject + end + + it 'handles missing blobs' do + expect { described_class.new(repository, 'non-existant-branch') }.not_to raise_error + end + + describe '#attributes' do + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.lfs')['filter']).to eq('lfs') + end + end +end diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb new file mode 100644 index 00000000000..323334e99a5 --- /dev/null +++ b/spec/lib/gitlab/git/attributes_parser_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +describe Gitlab::Git::AttributesParser, seed_helper: true do + let(:attributes_path) { File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info', 'attributes') } + let(:data) { File.read(attributes_path) } + + subject { described_class.new(data) } + + describe '#attributes' do + context 'using a path with attributes' do + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.txt')).to eq({ 'text' => true }) + end + + it 'returns a Hash containing multiple attributes' do + expect(subject.attributes('test.sh')) + .to eq({ 'eol' => 'lf', 'gitlab-language' => 'shell' }) + end + + it 'returns a Hash containing attributes for a file with multiple extensions' do + expect(subject.attributes('test.haml.html')) + .to eq({ 'gitlab-language' => 'haml' }) + end + + it 'returns a Hash containing attributes for a file in a directory' do + expect(subject.attributes('foo/bar.txt')).to eq({ 'foo' => true }) + end + + it 'returns a Hash containing attributes with query string parameters' do + expect(subject.attributes('foo.cgi')) + .to eq({ 'key' => 'value?p1=v1&p2=v2' }) + end + + it 'returns a Hash containing the attributes for an absolute path' do + expect(subject.attributes('/test.txt')).to eq({ 'text' => true }) + end + + it 'returns a Hash containing the attributes when a pattern is defined using an absolute path' do + # When a path is given without a leading slash it should still match + # patterns defined with a leading slash. + expect(subject.attributes('foo.png')) + .to eq({ 'gitlab-language' => 'png' }) + + expect(subject.attributes('/foo.png')) + .to eq({ 'gitlab-language' => 'png' }) + end + + it 'returns an empty Hash for a defined path without attributes' do + expect(subject.attributes('bla/bla.txt')).to eq({}) + end + + context 'when the "binary" option is set for a path' do + it 'returns true for the "binary" option' do + expect(subject.attributes('test.binary')['binary']).to eq(true) + end + + it 'returns false for the "diff" option' do + expect(subject.attributes('test.binary')['diff']).to eq(false) + end + end + end + + context 'using a path without any attributes' do + it 'returns an empty Hash' do + expect(subject.attributes('test.foo')).to eq({}) + end + end + + context 'when attributes data is a file handle' do + subject do + File.open(attributes_path, 'r') do |file_handle| + described_class.new(file_handle) + end + end + + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.txt')).to eq({ 'text' => true }) + end + end + + context 'when attributes data is nil' do + let(:data) { nil } + + it 'returns an empty Hash' do + expect(subject.attributes('test.foo')).to eq({}) + end + end + end + + describe '#patterns' do + it 'parses a file with entries' do + expect(subject.patterns).to be_an_instance_of(Hash) + end + + it 'parses an entry that uses a tab to separate the pattern and attributes' do + expect(subject.patterns[File.join('/', '*.md')]) + .to eq({ 'gitlab-language' => 'markdown' }) + end + + it 'stores patterns in reverse order' do + first = subject.patterns.to_a[0] + + expect(first[0]).to eq(File.join('/', 'bla/bla.txt')) + end + + # It's a bit hard to test for something _not_ being processed. As such we'll + # just test the number of entries. + it 'ignores any comments and empty lines' do + expect(subject.patterns.length).to eq(10) + end + end + + describe '#parse_attributes' do + it 'parses a boolean attribute' do + expect(subject.parse_attributes('text')).to eq({ 'text' => true }) + end + + it 'parses a negated boolean attribute' do + expect(subject.parse_attributes('-text')).to eq({ 'text' => false }) + end + + it 'parses a key-value pair' do + expect(subject.parse_attributes('foo=bar')).to eq({ 'foo' => 'bar' }) + end + + it 'parses multiple attributes' do + input = 'boolean key=value -negated' + + expect(subject.parse_attributes(input)) + .to eq({ 'boolean' => true, 'key' => 'value', 'negated' => false }) + end + + it 'parses attributes with query string parameters' do + expect(subject.parse_attributes('foo=bar?baz=1')) + .to eq({ 'foo' => 'bar?baz=1' }) + end + end + + describe '#each_line' do + it 'iterates over every line in the attributes file' do + args = [String] * 14 # the number of lines in the file + + expect { |b| subject.each_line(&b) }.to yield_successive_args(*args) + end + + it 'does not yield when the attributes file has an unsupported encoding' do + path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info', 'attributes') + attrs = described_class.new(File.read(path)) + + expect { |b| attrs.each_line(&b) }.not_to yield_control + end + end +end diff --git a/spec/lib/gitlab/git/attributes_spec.rb b/spec/lib/gitlab/git/attributes_spec.rb deleted file mode 100644 index b715fc3410a..00000000000 --- a/spec/lib/gitlab/git/attributes_spec.rb +++ /dev/null @@ -1,150 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Git::Attributes, seed_helper: true do - let(:path) do - File.join(SEED_STORAGE_PATH, 'with-git-attributes.git') - end - - subject { described_class.new(path) } - - describe '#attributes' do - context 'using a path with attributes' do - it 'returns the attributes as a Hash' do - expect(subject.attributes('test.txt')).to eq({ 'text' => true }) - end - - it 'returns a Hash containing multiple attributes' do - expect(subject.attributes('test.sh')) - .to eq({ 'eol' => 'lf', 'gitlab-language' => 'shell' }) - end - - it 'returns a Hash containing attributes for a file with multiple extensions' do - expect(subject.attributes('test.haml.html')) - .to eq({ 'gitlab-language' => 'haml' }) - end - - it 'returns a Hash containing attributes for a file in a directory' do - expect(subject.attributes('foo/bar.txt')).to eq({ 'foo' => true }) - end - - it 'returns a Hash containing attributes with query string parameters' do - expect(subject.attributes('foo.cgi')) - .to eq({ 'key' => 'value?p1=v1&p2=v2' }) - end - - it 'returns a Hash containing the attributes for an absolute path' do - expect(subject.attributes('/test.txt')).to eq({ 'text' => true }) - end - - it 'returns a Hash containing the attributes when a pattern is defined using an absolute path' do - # When a path is given without a leading slash it should still match - # patterns defined with a leading slash. - expect(subject.attributes('foo.png')) - .to eq({ 'gitlab-language' => 'png' }) - - expect(subject.attributes('/foo.png')) - .to eq({ 'gitlab-language' => 'png' }) - end - - it 'returns an empty Hash for a defined path without attributes' do - expect(subject.attributes('bla/bla.txt')).to eq({}) - end - - context 'when the "binary" option is set for a path' do - it 'returns true for the "binary" option' do - expect(subject.attributes('test.binary')['binary']).to eq(true) - end - - it 'returns false for the "diff" option' do - expect(subject.attributes('test.binary')['diff']).to eq(false) - end - end - end - - context 'using a path without any attributes' do - it 'returns an empty Hash' do - expect(subject.attributes('test.foo')).to eq({}) - end - end - end - - describe '#patterns' do - it 'parses a file with entries' do - expect(subject.patterns).to be_an_instance_of(Hash) - end - - it 'parses an entry that uses a tab to separate the pattern and attributes' do - expect(subject.patterns[File.join(path, '*.md')]) - .to eq({ 'gitlab-language' => 'markdown' }) - end - - it 'stores patterns in reverse order' do - first = subject.patterns.to_a[0] - - expect(first[0]).to eq(File.join(path, 'bla/bla.txt')) - end - - # It's a bit hard to test for something _not_ being processed. As such we'll - # just test the number of entries. - it 'ignores any comments and empty lines' do - expect(subject.patterns.length).to eq(10) - end - - it 'does not parse anything when the attributes file does not exist' do - expect(File).to receive(:exist?) - .with(File.join(path, 'info/attributes')) - .and_return(false) - - expect(subject.patterns).to eq({}) - end - end - - describe '#parse_attributes' do - it 'parses a boolean attribute' do - expect(subject.parse_attributes('text')).to eq({ 'text' => true }) - end - - it 'parses a negated boolean attribute' do - expect(subject.parse_attributes('-text')).to eq({ 'text' => false }) - end - - it 'parses a key-value pair' do - expect(subject.parse_attributes('foo=bar')).to eq({ 'foo' => 'bar' }) - end - - it 'parses multiple attributes' do - input = 'boolean key=value -negated' - - expect(subject.parse_attributes(input)) - .to eq({ 'boolean' => true, 'key' => 'value', 'negated' => false }) - end - - it 'parses attributes with query string parameters' do - expect(subject.parse_attributes('foo=bar?baz=1')) - .to eq({ 'foo' => 'bar?baz=1' }) - end - end - - describe '#each_line' do - it 'iterates over every line in the attributes file' do - args = [String] * 14 # the number of lines in the file - - expect { |b| subject.each_line(&b) }.to yield_successive_args(*args) - end - - it 'does not yield when the attributes file does not exist' do - expect(File).to receive(:exist?) - .with(File.join(path, 'info/attributes')) - .and_return(false) - - expect { |b| subject.each_line(&b) }.not_to yield_control - end - - it 'does not yield when the attributes file has an unsupported encoding' do - path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git') - attrs = described_class.new(path) - - expect { |b| attrs.each_line(&b) }.not_to yield_control - end - end -end diff --git a/spec/lib/gitlab/git/info_attributes_spec.rb b/spec/lib/gitlab/git/info_attributes_spec.rb new file mode 100644 index 00000000000..ea84909c3e0 --- /dev/null +++ b/spec/lib/gitlab/git/info_attributes_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe Gitlab::Git::InfoAttributes, seed_helper: true do + let(:path) do + File.join(SEED_STORAGE_PATH, 'with-git-attributes.git') + end + + subject { described_class.new(path) } + + describe '#attributes' do + context 'using a path with attributes' do + it 'returns the attributes as a Hash' do + expect(subject.attributes('test.txt')).to eq({ 'text' => true }) + end + + it 'returns an empty Hash for a defined path without attributes' do + expect(subject.attributes('bla/bla.txt')).to eq({}) + end + end + end + + describe '#parser' do + it 'parses a file with entries' do + expect(subject.patterns).to be_an_instance_of(Hash) + expect(subject.patterns["/*.txt"]).to eq({ 'text' => true }) + end + + it 'does not parse anything when the attributes file does not exist' do + expect(File).to receive(:exist?) + .with(File.join(path, 'info/attributes')) + .and_return(false) + + expect(subject.patterns).to eq({}) + end + + it 'does not parse attributes files with unsupported encoding' do + path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git') + subject = described_class.new(path) + + expect(subject.patterns).to eq({}) + end + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 664698fcbaf..a00ef543128 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -20,7 +20,7 @@ module TestEnv 'improve/awesome' => '5937ac0', 'merged-target' => '21751bf', 'markdown' => '0ed8c6c', - 'lfs' => 'be93687', + 'lfs' => '55bc176', 'master' => 'b83d6e3', 'merge-test' => '5937ac0', "'test'" => 'e56497b', -- cgit v1.2.1 From ac5cbb6cd66a41e2d77a86bcda743066714194f6 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 23 Jan 2018 18:17:44 -0600 Subject: disable CopyAsGFM on iOS due to bug in webkit --- app/assets/javascripts/behaviors/copy_as_gfm.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/javascripts/behaviors/copy_as_gfm.js b/app/assets/javascripts/behaviors/copy_as_gfm.js index c6eca72c51b..ffe90595b5d 100644 --- a/app/assets/javascripts/behaviors/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/copy_as_gfm.js @@ -299,6 +299,13 @@ const gfmRules = { export class CopyAsGFM { constructor() { + // iOS currently does not support clipboardData.setData(). This bug should + // be fixed in iOS 12, but for now we'll disable this for all iOS browsers + // ref: https://trac.webkit.org/changeset/222228/webkit + const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || ''; + const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent); + if (isIOS) return; + $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM); -- cgit v1.2.1 From 194e1b8f32618e60af0cf7767b6facecce40d74e Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 23 Jan 2018 18:20:10 -0600 Subject: add CHANGELOG.md entry for !15804 --- changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml diff --git a/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml b/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml new file mode 100644 index 00000000000..f4c44983736 --- /dev/null +++ b/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml @@ -0,0 +1,5 @@ +--- +title: Fix copy/paste on iOS devices due to a bug in webkit +merge_request: 15804 +author: +type: fixed -- cgit v1.2.1 From 79a829a0372a974bb3d40e66ca3fdc213200db40 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 23 Jan 2018 22:02:33 -0800 Subject: Return a blank JSON response for a missing .js file to prevent Rails CSRF errors The default 404 handler would return the Content-Type format based on the given format extension. This would cause the Rails CSRF protection to flag an error, since the .js extension gets mapped to text/javascript format. Closes #40771 --- app/controllers/application_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ee21d81f23e..95ad38d9230 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -147,6 +147,8 @@ class ApplicationController < ActionController::Base format.html do render file: Rails.root.join("public", "404"), layout: false, status: "404" end + # Prevent the Rails CSRF protector from thinking a missing .js file is a JavaScript file + format.js { render json: '', status: :not_found, content_type: 'application/json' } format.any { head :not_found } end end -- cgit v1.2.1 From 81bbcfacb0adfa32b15b044bfb997aca7bed69fb Mon Sep 17 00:00:00 2001 From: Nicolas MERELLI Date: Wed, 4 Jan 2017 23:07:49 +0100 Subject: Add application create API --- .../unreleased/24035-api-create-application.yml | 4 + doc/api/applications.md | 33 +++++++++ lib/api/api.rb | 1 + lib/api/applications.rb | 26 +++++++ lib/api/entities.rb | 6 ++ spec/requests/api/applications_spec.rb | 86 ++++++++++++++++++++++ 6 files changed, 156 insertions(+) create mode 100644 changelogs/unreleased/24035-api-create-application.yml create mode 100644 doc/api/applications.md create mode 100644 lib/api/applications.rb create mode 100644 spec/requests/api/applications_spec.rb diff --git a/changelogs/unreleased/24035-api-create-application.yml b/changelogs/unreleased/24035-api-create-application.yml new file mode 100644 index 00000000000..c583a020d9d --- /dev/null +++ b/changelogs/unreleased/24035-api-create-application.yml @@ -0,0 +1,4 @@ +--- +title: Add application create API +merge_request: 8160 +author: Nicolas Merelli @PNSalocin diff --git a/doc/api/applications.md b/doc/api/applications.md new file mode 100644 index 00000000000..637e52f2b57 --- /dev/null +++ b/doc/api/applications.md @@ -0,0 +1,33 @@ +# Applications API + +## Create a application + +Create a application by posting a JSON payload. + +User must be admin to do that. + +Returns `200` if the request succeeds. + +``` +POST /applications +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | yes | The name of the application | +| `redirect_uri` | string | yes | The redirect URI of the application | +| `scopes` | string | yes | The scopes of the application | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=MyApplication&redirect_uri=http://redirect.uri&scopes=" https://gitlab.example.com/api/v3/applications +``` + +Example response: + +```json +{ + "application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737", + "secret": "ee1dd64b6adc89cf7e2c23099301ccc2c61b441064e9324d963c46902a85ec34", + "callback_url": "http://redirect.uri" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index ae161efb358..f3f64244589 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -106,6 +106,7 @@ module API # Keep in alphabetical order mount ::API::AccessRequests + mount ::API::Applications mount ::API::AwardEmoji mount ::API::Boards mount ::API::Branches diff --git a/lib/api/applications.rb b/lib/api/applications.rb new file mode 100644 index 00000000000..063f8efab97 --- /dev/null +++ b/lib/api/applications.rb @@ -0,0 +1,26 @@ +module API + # External applications API + class Applications < Grape::API + before { authenticated_as_admin! } + + resource :applications do + desc 'Create a new application' do + success Entities::Application + end + params do + requires :name, type: String, desc: 'Application name' + requires :redirect_uri, type: String, desc: 'Application redirect URI' + requires :scopes, type: String, desc: 'Application scopes' + end + post do + application = Doorkeeper::Application.new(declared_params) + + if application.save + present application, with: Entities::Application + else + render_validation_error! application + end + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3f4b62dc1b2..cfe9a8704bc 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1157,5 +1157,11 @@ module API pages_domain end end + + class Application < Grape::Entity + expose :uid, as: :application_id + expose :secret + expose :redirect_uri, as: :callback_url + end end end diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb new file mode 100644 index 00000000000..f56bc932f40 --- /dev/null +++ b/spec/requests/api/applications_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe API::Applications, :api do + include ApiHelpers + + let(:admin_user) { create(:user, admin: true) } + let(:user) { create(:user, admin: false) } + + describe 'POST /applications' do + context 'authenticated and authorized user' do + it 'creates and returns an OAuth application' do + expect do + post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url', scopes: '' + end.to change { Doorkeeper::Application.count }.by 1 + + application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url') + + expect(response).to have_http_status 201 + expect(json_response).to be_a Hash + expect(json_response['application_id']).to eq application.uid + expect(json_response['secret']).to eq application.secret + expect(json_response['callback_url']).to eq application.redirect_uri + end + + it 'does not allow creating an application with the wrong redirect_uri format' do + expect do + post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: '' + end.not_to change { Doorkeeper::Application.count } + + expect(response).to have_http_status 400 + expect(json_response).to be_a Hash + expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.') + end + + it 'does not allow creating an application without a name' do + expect do + post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: '' + end.not_to change { Doorkeeper::Application.count } + + expect(response).to have_http_status 400 + expect(json_response).to be_a Hash + expect(json_response['error']).to eq('name is missing') + end + + it 'does not allow creating an application without a redirect_uri' do + expect do + post api('/applications', admin_user), name: 'application_name', scopes: '' + end.not_to change { Doorkeeper::Application.count } + + expect(response).to have_http_status 400 + expect(json_response).to be_a Hash + expect(json_response['error']).to eq('redirect_uri is missing') + end + + it 'does not allow creating an application without scopes' do + expect do + post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url' + end.not_to change { Doorkeeper::Application.count } + + expect(response).to have_http_status 400 + expect(json_response).to be_a Hash + expect(json_response['error']).to eq('scopes is missing') + end + end + + context 'authorized user without authorization' do + it 'does not create application' do + expect do + post api('/applications', user), name: 'application_name', redirect_uri: 'http://application.url', scopes: '' + end.not_to change { Doorkeeper::Application.count } + + expect(response).to have_http_status 403 + end + end + + context 'non-authenticated user' do + it 'does not create application' do + expect do + post api('/applications'), name: 'application_name', redirect_uri: 'http://application.url' + end.not_to change { Doorkeeper::Application.count } + + expect(response).to have_http_status 401 + end + end + end +end -- cgit v1.2.1 From d38faa30ed6fb79964b25fce843cb57db1bdb198 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Tue, 23 Jan 2018 10:50:10 +0100 Subject: Add documentation about when the application API was added --- doc/api/applications.md | 6 +++++- lib/api/applications.rb | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/api/applications.md b/doc/api/applications.md index 637e52f2b57..933867ed0bb 100644 --- a/doc/api/applications.md +++ b/doc/api/applications.md @@ -1,5 +1,9 @@ # Applications API +> [Introduced][ce-8160] in GitLab 10.5 + +[ce-8160]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160 + ## Create a application Create a application by posting a JSON payload. @@ -25,7 +29,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name= Example response: ```json -{ +{ "application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737", "secret": "ee1dd64b6adc89cf7e2c23099301ccc2c61b441064e9324d963c46902a85ec34", "callback_url": "http://redirect.uri" diff --git a/lib/api/applications.rb b/lib/api/applications.rb index 063f8efab97..142ba73a53d 100644 --- a/lib/api/applications.rb +++ b/lib/api/applications.rb @@ -5,6 +5,7 @@ module API resource :applications do desc 'Create a new application' do + detail 'This feature was introduced in GitLab 10.5' success Entities::Application end params do -- cgit v1.2.1 From 45b62dfd324318959ff6fa37f9d3f8a1a95b4aa7 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Wed, 24 Jan 2018 09:44:07 +0100 Subject: Make the exposing of the Application secret more explicit To make it more clear to developers that the entity exposes the application secret, define a separate entity that only should be used when the secret is needed (probably only on creation). --- lib/api/applications.rb | 4 ++-- lib/api/entities.rb | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/api/applications.rb b/lib/api/applications.rb index 142ba73a53d..b122cdefe4e 100644 --- a/lib/api/applications.rb +++ b/lib/api/applications.rb @@ -6,7 +6,7 @@ module API resource :applications do desc 'Create a new application' do detail 'This feature was introduced in GitLab 10.5' - success Entities::Application + success Entities::ApplicationWithSecret end params do requires :name, type: String, desc: 'Application name' @@ -17,7 +17,7 @@ module API application = Doorkeeper::Application.new(declared_params) if application.save - present application, with: Entities::Application + present application, with: Entities::ApplicationWithSecret else render_validation_error! application end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cfe9a8704bc..7b9a80a234b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1160,8 +1160,12 @@ module API class Application < Grape::Entity expose :uid, as: :application_id - expose :secret expose :redirect_uri, as: :callback_url end + + # Use with care, this exposes the secret + class ApplicationWithSecret < Application + expose :secret + end end end -- cgit v1.2.1 From adf31a57f47215813e36c573c98bb11ab608cee9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 23 Jan 2018 09:11:41 +0000 Subject: Added imports for dispatcher routes --- app/assets/javascripts/dispatcher.js | 20 +++----------------- app/assets/javascripts/pages/projects/show/index.js | 4 ++++ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d5659be28a4..f9e23189774 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,13 +1,8 @@ /* 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 notificationsDropdown from './notifications_dropdown'; -import LineHighlighter from './line_highlighter'; import MergeRequest from './merge_request'; import Flash from './flash'; -import BlobViewer from './blob/viewer/index'; import GfmAutoComplete from './gfm_auto_complete'; -import Star from './star'; import ZenMode from './zen_mode'; -import PerformanceBar from './performance_bar'; import initNotes from './init_notes'; import initIssuableSidebar from './init_issuable_sidebar'; import { convertPermissionToBoolean } from './lib/utils/common_utils'; @@ -621,23 +616,12 @@ import SearchAutocomplete from './search_autocomplete'; .then(callDefault) .catch(fail); break; - case 'show': - new Star(); - notificationsDropdown(); - break; case 'wikis': import('./pages/projects/wikis') .then(callDefault) .catch(fail); shortcut_handler = true; break; - case 'snippets': - if (path[2] === 'show') { - new ZenMode(); - new LineHighlighter(); - new BlobViewer(); - } - break; } break; } @@ -647,7 +631,9 @@ import SearchAutocomplete from './search_autocomplete'; } if (document.querySelector('#peek')) { - new PerformanceBar({ container: '#peek' }); + import('./performance_bar') + .then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap + .catch(fail); } }; diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index 92dc1e59651..55154cdddcb 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -5,8 +5,12 @@ import TreeView from '~/tree'; import BlobViewer from '~/blob/viewer/index'; import Activities from '~/activities'; import { ajaxGet } from '~/lib/utils/common_utils'; +import Star from '../../../star'; +import notificationsDropdown from '../../../notifications_dropdown'; export default () => { + new Star(); // eslint-disable-line no-new + notificationsDropdown(); new ShortcutsNavigation(); // eslint-disable-line no-new new NotificationsForm(); // eslint-disable-line no-new new UserCallout({ // eslint-disable-line no-new -- cgit v1.2.1 From aaf03c0c4819a5839f2dbd5003a7ce066b68ee53 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 23 Jan 2018 09:17:29 +0000 Subject: correctly imports performance bar --- app/views/layouts/_head.html.haml | 1 - config/webpack.config.js | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 1597621fa78..ea13a5e6d62 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -43,7 +43,6 @@ = webpack_bundle_tag "main" = webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled = webpack_bundle_tag "test" if Rails.env.test? - = webpack_bundle_tag 'performance_bar' if performance_bar_enabled? - if content_for?(:page_specific_javascripts) = yield :page_specific_javascripts diff --git a/config/webpack.config.js b/config/webpack.config.js index 229db11acb2..26502efaf6c 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -85,7 +85,6 @@ var config = { test: './test.js', two_factor_auth: './two_factor_auth.js', users: './users/index.js', - performance_bar: './performance_bar.js', webpack_runtime: './webpack.js', }, @@ -119,9 +118,9 @@ var config = { { test: /\_worker\.js$/, use: [ - { + { loader: 'worker-loader', - options: { + options: { inline: true } }, -- cgit v1.2.1 From e22c007f5093af53086284c8bd59ad7c949c6da1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 23 Jan 2018 11:30:06 +0000 Subject: added missing imports --- app/assets/javascripts/pages/projects/snippets/show/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js index d8cf5184f8f..a3cf75c385b 100644 --- a/app/assets/javascripts/pages/projects/snippets/show/index.js +++ b/app/assets/javascripts/pages/projects/snippets/show/index.js @@ -1,7 +1,11 @@ import initNotes from '~/init_notes'; import ZenMode from '~/zen_mode'; +import LineHighlighter from '../../../../line_highlighter'; +import BlobViewer from '../../../../blob/viewer'; export default function () { + new LineHighlighter(); // eslint-disable-line no-new + new BlobViewer(); // eslint-disable-line no-new initNotes(); new ZenMode(); // eslint-disable-line no-new } -- cgit v1.2.1 From 24625323a826d70991fdf27fbbe1f39bee845c03 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 18 Jan 2018 17:33:35 +0000 Subject: Migrate repository bundling to Gitaly Closes gitaly#929 --- GITALY_SERVER_VERSION | 2 +- Gemfile | 2 +- Gemfile.lock | 4 ++-- app/models/repository.rb | 1 + lib/gitlab/git/repository.rb | 12 +++++++++++ lib/gitlab/gitaly_client/repository_service.rb | 17 ++++++++++++++++ lib/gitlab/import_export/command_line_util.rb | 4 ---- lib/gitlab/import_export/repo_saver.rb | 2 +- lib/gitlab/import_export/wiki_repo_saver.rb | 2 +- spec/lib/gitlab/git/repository_spec.rb | 28 ++++++++++++++++++++++++++ 10 files changed, 64 insertions(+), 10 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 7375dee5f49..31b648bd6fa 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.72.0 +0.73.0 diff --git a/Gemfile b/Gemfile index a4c3db4bf5f..346182b3852 100644 --- a/Gemfile +++ b/Gemfile @@ -406,7 +406,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.74.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.76.0', require: 'gitaly' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index d69c532b309..1cbeab8d6b5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -285,7 +285,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.74.0) + gitaly-proto (0.76.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -1056,7 +1056,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.74.0) + gitaly-proto (~> 0.76.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) diff --git a/app/models/repository.rb b/app/models/repository.rb index 73c4899cb9b..824e18bec78 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -20,6 +20,7 @@ class Repository attr_accessor :full_path, :disk_path, :project, :is_wiki delegate :ref_name_for_sha, to: :raw_repository + delegate :bundle_to_disk, to: :raw_repository CreateTreeError = Class.new(StandardError) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index d666362de0d..9864e4251f1 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1268,6 +1268,18 @@ module Gitlab success || gitlab_projects_error end + def bundle_to_disk(save_path) + gitaly_migrate(:bundle_to_disk) do |is_enabled| + if is_enabled + gitaly_repository_client.create_bundle(save_path) + else + run_git!(%W(bundle create #{save_path} --all)) + end + end + + true + end + # rubocop:disable Metrics/ParameterLists def multi_action( user, branch_name:, message:, actions:, diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 12016aee2a6..654a3c314f1 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -161,6 +161,23 @@ module Gitlab return response.error.b, 1 end end + + def create_bundle(save_path) + request = Gitaly::CreateBundleRequest.new(repository: @gitaly_repo) + response = GitalyClient.call( + @storage, + :repository_service, + :create_bundle, + request, + timeout: GitalyClient.default_timeout + ) + + File.open(save_path, 'wb') do |f| + response.each do |message| + f.write(message.data) + end + end + end end end end diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index dd5d35feab9..25399f307f2 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -11,10 +11,6 @@ module Gitlab untar_with_options(archive: archive, dir: dir, options: 'zxf') end - def git_bundle(repo_path:, bundle_path:) - execute(%W(#{git_bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all)) - end - def git_clone_bundle(repo_path:, bundle_path:) execute(%W(#{git_bin_path} clone --bare -- #{bundle_path} #{repo_path})) Gitlab::Git::Repository.create_hooks(repo_path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path)) diff --git a/lib/gitlab/import_export/repo_saver.rb b/lib/gitlab/import_export/repo_saver.rb index a7028a32570..695462c7dd2 100644 --- a/lib/gitlab/import_export/repo_saver.rb +++ b/lib/gitlab/import_export/repo_saver.rb @@ -21,7 +21,7 @@ module Gitlab def bundle_to_disk mkdir_p(@shared.export_path) - git_bundle(repo_path: path_to_repo, bundle_path: @full_path) + @project.repository.bundle_to_disk(@full_path) rescue => e @shared.error(e) false diff --git a/lib/gitlab/import_export/wiki_repo_saver.rb b/lib/gitlab/import_export/wiki_repo_saver.rb index 1e6722a7bba..5fa2e101e29 100644 --- a/lib/gitlab/import_export/wiki_repo_saver.rb +++ b/lib/gitlab/import_export/wiki_repo_saver.rb @@ -10,7 +10,7 @@ module Gitlab def bundle_to_disk(full_path) mkdir_p(@shared.export_path) - git_bundle(repo_path: path_to_repo, bundle_path: full_path) + @wiki.repository.bundle_to_disk(full_path) rescue => e @shared.error(e) false diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index aec7cde6df8..36ca3980de9 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1926,6 +1926,34 @@ describe Gitlab::Git::Repository, seed_helper: true do it { expect(subject.repository_relative_path).to eq(repository.relative_path) } end + describe '#bundle_to_disk' do + shared_examples 'bundling to disk' do + let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") } + + after do + FileUtils.rm_rf(save_path) + end + + it 'saves a bundle to disk' do + repository.bundle_to_disk(save_path) + + success = system( + *%W(#{Gitlab.config.git.bin_path} -C #{repository.path} bundle verify #{save_path}), + [:out, :err] => '/dev/null' + ) + expect(success).to be true + end + end + + context 'when Gitaly bundle_to_disk feature is enabled' do + it_behaves_like 'bundling to disk' + end + + context 'when Gitaly bundle_to_disk feature is disabled', :disable_gitaly do + it_behaves_like 'bundling to disk' + end + end + context 'gitlab_projects commands' do let(:gitlab_projects) { repository.gitlab_projects } let(:timeout) { Gitlab.config.gitlab_shell.git_timeout } -- cgit v1.2.1 From 8a852dee62bdd34ca2f484a8f6767663fdb2416f Mon Sep 17 00:00:00 2001 From: Mathijs de Kruyf Date: Wed, 24 Jan 2018 12:03:12 +0000 Subject: Fix typo in `.gitlab-ci.yml` heading --- doc/ci/variables/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 06f69938aae..598a7515b01 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -110,7 +110,7 @@ future GitLab releases.** | `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` | | `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` | -## `.gitlab-ci.yaml` defined variables +## `.gitlab-ci.yml` defined variables >**Note:** This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher. -- cgit v1.2.1 From 38ef39550c18124f7a62568900ffff799abf5b97 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 24 Jan 2018 11:46:18 -0200 Subject: Remove callback as we already update accordingly on services --- app/models/merge_request.rb | 7 ------- spec/models/merge_request_spec.rb | 17 ----------------- 2 files changed, 24 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4addf42325d..8028ff3875b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -56,7 +56,6 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :clear_memoized_shas after_update :reload_diff_if_branch_changed - after_update :mark_as_unchecked_if_target_branch_changed # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -562,12 +561,6 @@ class MergeRequest < ActiveRecord::Base end end - def mark_as_unchecked_if_target_branch_changed - return unless target_branch_changed? - - mark_as_unchecked - end - def reload_diff(current_user = nil) return unless open? diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 7cc237937f3..c76f32b3989 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -77,23 +77,6 @@ describe MergeRequest do expect(MergeRequest::Metrics.count).to eq(1) end end - - describe '#mark_as_unchecked_if_target_branch_changed' do - let(:merge_request) { create(:merge_request, merge_status: :can_be_merged) } - - it 'marks MR as unchecked if target_branch changes' do - expect { merge_request.update!(target_branch: 'bar') } - .to change(merge_request, :merge_status) - .from('can_be_merged') - .to('unchecked') - end - - it 'does not marks MR as unchecked when target_branch does not changes' do - expect { merge_request.update!(title: 'foo') } - .not_to change(merge_request, :merge_status) - .from('can_be_merged') - end - end end describe 'respond to' do -- cgit v1.2.1 From 4a3e22a9c0d892dfaeba1ef2d40271db967eb0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 24 Jan 2018 15:03:59 +0100 Subject: Remove one Spinach job and add one RSpec job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 126 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bb1c84ab918..349ea49fe8f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -322,69 +322,69 @@ setup-test-env: paths: - tmp/tests -rspec-pg 0 26: *rspec-metadata-pg -rspec-pg 1 26: *rspec-metadata-pg -rspec-pg 2 26: *rspec-metadata-pg -rspec-pg 3 26: *rspec-metadata-pg -rspec-pg 4 26: *rspec-metadata-pg -rspec-pg 5 26: *rspec-metadata-pg -rspec-pg 6 26: *rspec-metadata-pg -rspec-pg 7 26: *rspec-metadata-pg -rspec-pg 8 26: *rspec-metadata-pg -rspec-pg 9 26: *rspec-metadata-pg -rspec-pg 10 26: *rspec-metadata-pg -rspec-pg 11 26: *rspec-metadata-pg -rspec-pg 12 26: *rspec-metadata-pg -rspec-pg 13 26: *rspec-metadata-pg -rspec-pg 14 26: *rspec-metadata-pg -rspec-pg 15 26: *rspec-metadata-pg -rspec-pg 16 26: *rspec-metadata-pg -rspec-pg 17 26: *rspec-metadata-pg -rspec-pg 18 26: *rspec-metadata-pg -rspec-pg 19 26: *rspec-metadata-pg -rspec-pg 20 26: *rspec-metadata-pg -rspec-pg 21 26: *rspec-metadata-pg -rspec-pg 22 26: *rspec-metadata-pg -rspec-pg 23 26: *rspec-metadata-pg -rspec-pg 24 26: *rspec-metadata-pg -rspec-pg 25 26: *rspec-metadata-pg - -rspec-mysql 0 26: *rspec-metadata-mysql -rspec-mysql 1 26: *rspec-metadata-mysql -rspec-mysql 2 26: *rspec-metadata-mysql -rspec-mysql 3 26: *rspec-metadata-mysql -rspec-mysql 4 26: *rspec-metadata-mysql -rspec-mysql 5 26: *rspec-metadata-mysql -rspec-mysql 6 26: *rspec-metadata-mysql -rspec-mysql 7 26: *rspec-metadata-mysql -rspec-mysql 8 26: *rspec-metadata-mysql -rspec-mysql 9 26: *rspec-metadata-mysql -rspec-mysql 10 26: *rspec-metadata-mysql -rspec-mysql 11 26: *rspec-metadata-mysql -rspec-mysql 12 26: *rspec-metadata-mysql -rspec-mysql 13 26: *rspec-metadata-mysql -rspec-mysql 14 26: *rspec-metadata-mysql -rspec-mysql 15 26: *rspec-metadata-mysql -rspec-mysql 16 26: *rspec-metadata-mysql -rspec-mysql 17 26: *rspec-metadata-mysql -rspec-mysql 18 26: *rspec-metadata-mysql -rspec-mysql 19 26: *rspec-metadata-mysql -rspec-mysql 20 26: *rspec-metadata-mysql -rspec-mysql 21 26: *rspec-metadata-mysql -rspec-mysql 22 26: *rspec-metadata-mysql -rspec-mysql 23 26: *rspec-metadata-mysql -rspec-mysql 24 26: *rspec-metadata-mysql -rspec-mysql 25 26: *rspec-metadata-mysql - -spinach-pg 0 4: *spinach-metadata-pg -spinach-pg 1 4: *spinach-metadata-pg -spinach-pg 2 4: *spinach-metadata-pg -spinach-pg 3 4: *spinach-metadata-pg - -spinach-mysql 0 4: *spinach-metadata-mysql -spinach-mysql 1 4: *spinach-metadata-mysql -spinach-mysql 2 4: *spinach-metadata-mysql -spinach-mysql 3 4: *spinach-metadata-mysql +rspec-pg 0 27: *rspec-metadata-pg +rspec-pg 1 27: *rspec-metadata-pg +rspec-pg 2 27: *rspec-metadata-pg +rspec-pg 3 27: *rspec-metadata-pg +rspec-pg 4 27: *rspec-metadata-pg +rspec-pg 5 27: *rspec-metadata-pg +rspec-pg 6 27: *rspec-metadata-pg +rspec-pg 7 27: *rspec-metadata-pg +rspec-pg 8 27: *rspec-metadata-pg +rspec-pg 9 27: *rspec-metadata-pg +rspec-pg 10 27: *rspec-metadata-pg +rspec-pg 11 27: *rspec-metadata-pg +rspec-pg 12 27: *rspec-metadata-pg +rspec-pg 13 27: *rspec-metadata-pg +rspec-pg 14 27: *rspec-metadata-pg +rspec-pg 15 27: *rspec-metadata-pg +rspec-pg 16 27: *rspec-metadata-pg +rspec-pg 17 27: *rspec-metadata-pg +rspec-pg 18 27: *rspec-metadata-pg +rspec-pg 19 27: *rspec-metadata-pg +rspec-pg 20 27: *rspec-metadata-pg +rspec-pg 21 27: *rspec-metadata-pg +rspec-pg 22 27: *rspec-metadata-pg +rspec-pg 23 27: *rspec-metadata-pg +rspec-pg 24 27: *rspec-metadata-pg +rspec-pg 25 27: *rspec-metadata-pg +rspec-pg 26 27: *rspec-metadata-pg + +rspec-mysql 0 27: *rspec-metadata-mysql +rspec-mysql 1 27: *rspec-metadata-mysql +rspec-mysql 2 27: *rspec-metadata-mysql +rspec-mysql 3 27: *rspec-metadata-mysql +rspec-mysql 4 27: *rspec-metadata-mysql +rspec-mysql 5 27: *rspec-metadata-mysql +rspec-mysql 6 27: *rspec-metadata-mysql +rspec-mysql 7 27: *rspec-metadata-mysql +rspec-mysql 8 27: *rspec-metadata-mysql +rspec-mysql 9 27: *rspec-metadata-mysql +rspec-mysql 10 27: *rspec-metadata-mysql +rspec-mysql 11 27: *rspec-metadata-mysql +rspec-mysql 12 27: *rspec-metadata-mysql +rspec-mysql 13 27: *rspec-metadata-mysql +rspec-mysql 14 27: *rspec-metadata-mysql +rspec-mysql 15 27: *rspec-metadata-mysql +rspec-mysql 16 27: *rspec-metadata-mysql +rspec-mysql 17 27: *rspec-metadata-mysql +rspec-mysql 18 27: *rspec-metadata-mysql +rspec-mysql 19 27: *rspec-metadata-mysql +rspec-mysql 20 27: *rspec-metadata-mysql +rspec-mysql 21 27: *rspec-metadata-mysql +rspec-mysql 22 27: *rspec-metadata-mysql +rspec-mysql 23 27: *rspec-metadata-mysql +rspec-mysql 24 27: *rspec-metadata-mysql +rspec-mysql 25 27: *rspec-metadata-mysql +rspec-mysql 26 27: *rspec-metadata-mysql + +spinach-pg 0 3: *spinach-metadata-pg +spinach-pg 1 3: *spinach-metadata-pg +spinach-pg 2 3: *spinach-metadata-pg + +spinach-mysql 0 3: *spinach-metadata-mysql +spinach-mysql 1 3: *spinach-metadata-mysql +spinach-mysql 2 3: *spinach-metadata-mysql # Static analysis jobs .ruby-static-analysis: &ruby-static-analysis -- cgit v1.2.1 From 4564795195f4dafa3ff0578565fc13234b164a97 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 24 Jan 2018 15:09:59 +0100 Subject: Execute system hooks after-commit when executing project hooks --- app/models/project.rb | 4 ++-- .../dm-project-system-hooks-in-transaction.yml | 5 +++++ spec/models/project_spec.rb | 17 +++++++++++++++++ spec/services/issues/move_service_spec.rb | 4 +++- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/dm-project-system-hooks-in-transaction.yml diff --git a/app/models/project.rb b/app/models/project.rb index 0570bbc8ee3..e19873f64ce 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -971,9 +971,9 @@ class Project < ActiveRecord::Base hooks.hooks_for(hooks_scope).each do |hook| hook.async_execute(data, hooks_scope.to_s) end - end - SystemHooksService.new.execute_hooks(data, hooks_scope) + SystemHooksService.new.execute_hooks(data, hooks_scope) + end end def execute_services(data, hooks_scope = :push_hooks) diff --git a/changelogs/unreleased/dm-project-system-hooks-in-transaction.yml b/changelogs/unreleased/dm-project-system-hooks-in-transaction.yml new file mode 100644 index 00000000000..f59021c0ec9 --- /dev/null +++ b/changelogs/unreleased/dm-project-system-hooks-in-transaction.yml @@ -0,0 +1,5 @@ +--- +title: Execute system hooks after-commit when executing project hooks +merge_request: +author: +type: fixed diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 4d10df410ab..31dcb543cbd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3228,5 +3228,22 @@ describe Project do project = build(:project) project.execute_hooks({ data: 'data' }, :merge_request_hooks) end + + it 'executes the system hooks when inside a transaction' do + allow_any_instance_of(WebHookService).to receive(:execute) + + create(:system_hook, merge_requests_events: true) + + project = build(:project) + + # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1, + # but since the entire spec run takes place in a transaction, we never + # actually get to the `after_commit` hook that queues these jobs. + expect do + project.transaction do + project.execute_hooks({ data: 'data' }, :merge_request_hooks) + end + end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError + end end end diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index f3c98fa5416..388c9d63c7b 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -297,9 +297,11 @@ describe Issues::MoveService do end context 'project issue hooks' do - let(:hook) { create(:project_hook, project: old_project, issues_events: true) } + let!(:hook) { create(:project_hook, project: old_project, issues_events: true) } it 'executes project issue hooks' do + allow_any_instance_of(WebHookService).to receive(:execute) + # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1, # but since the entire spec run takes place in a transaction, we never # actually get to the `after_commit` hook that queues these jobs. -- cgit v1.2.1 From 09babc575473eadc4ffabe3d469f95d4347cf76c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 24 Jan 2018 13:24:12 +0000 Subject: Moves more mr widget components into vue files Adds i18n Adds better test coverage --- .../components/states/mr_widget_checking.js | 18 --- .../components/states/mr_widget_checking.vue | 23 ++++ .../components/states/mr_widget_closed.js | 35 ------ .../components/states/mr_widget_closed.vue | 48 +++++++ .../components/states/mr_widget_conflicts.js | 47 ------- .../components/states/mr_widget_conflicts.vue | 61 +++++++++ .../vue_merge_request_widget/dependencies.js | 6 +- changelogs/unreleased/fl-mr-widget-refactor.yml | 5 + .../components/states/mr_widget_checking_spec.js | 36 ++++-- .../components/states/mr_widget_closed_spec.js | 106 +++++++--------- .../components/states/mr_widget_conflicts_spec.js | 138 +++++++++------------ 11 files changed, 267 insertions(+), 256 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue create mode 100644 changelogs/unreleased/fl-mr-widget-refactor.yml diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js deleted file mode 100644 index 09561694939..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js +++ /dev/null @@ -1,18 +0,0 @@ -import statusIcon from '../mr_widget_status_icon'; - -export default { - name: 'MRWidgetChecking', - components: { - statusIcon, - }, - template: ` -
- -
- - Checking ability to merge automatically - -
-
- `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue new file mode 100644 index 00000000000..04e1766b8c7 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue @@ -0,0 +1,23 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js deleted file mode 100644 index dc19b20aa11..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js +++ /dev/null @@ -1,35 +0,0 @@ -import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; -import statusIcon from '../mr_widget_status_icon'; - -export default { - name: 'MRWidgetClosed', - props: { - mr: { type: Object, required: true }, - }, - components: { - 'mr-widget-author-and-time': mrWidgetAuthorTime, - statusIcon, - }, - template: ` -
- -
- -
-

- The changes were not merged into - - {{mr.targetBranch}} -

-
-
-
- `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue new file mode 100644 index 00000000000..8c0ce43c76c --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue @@ -0,0 +1,48 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js deleted file mode 100644 index 7a887bacfa7..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js +++ /dev/null @@ -1,47 +0,0 @@ -import statusIcon from '../mr_widget_status_icon'; - -export default { - name: 'MRWidgetConflicts', - props: { - mr: { type: Object, required: true }, - }, - components: { - statusIcon, - }, - template: ` -
- -
- - Fast-forward merge is not possible. - To merge this request, first rebase locally. - - -
-
- `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue new file mode 100644 index 00000000000..13b07f82330 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue @@ -0,0 +1,61 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 2dd3b2c2f98..5e8e251428a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -18,11 +18,11 @@ export { default as WidgetDeployment } from './components/mr_widget_deployment'; export { default as WidgetRelatedLinks } from './components/mr_widget_related_links'; export { default as MergedState } from './components/states/mr_widget_merged'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge'; -export { default as ClosedState } from './components/states/mr_widget_closed'; +export { default as ClosedState } from './components/states/mr_widget_closed.vue'; export { default as MergingState } from './components/states/mr_widget_merging'; export { default as WipState } from './components/states/mr_widget_wip'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; -export { default as ConflictsState } from './components/states/mr_widget_conflicts'; +export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue'; export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed'; @@ -34,7 +34,7 @@ export { default as PipelineFailedState } from './components/states/mr_widget_pi export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds'; export { default as RebaseState } from './components/states/mr_widget_rebase.vue'; export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue'; -export { default as CheckingState } from './components/states/mr_widget_checking'; +export { default as CheckingState } from './components/states/mr_widget_checking.vue'; export { default as MRWidgetStore } from './stores/mr_widget_store'; export { default as MRWidgetService } from './services/mr_widget_service'; export { default as eventHub } from './event_hub'; diff --git a/changelogs/unreleased/fl-mr-widget-refactor.yml b/changelogs/unreleased/fl-mr-widget-refactor.yml new file mode 100644 index 00000000000..d59cca68409 --- /dev/null +++ b/changelogs/unreleased/fl-mr-widget-refactor.yml @@ -0,0 +1,5 @@ +--- +title: Refactors mr widget components into vue files and adds i18n +merge_request: +author: +type: other diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js index 6b7aa935ad3..658cadddb81 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js @@ -1,19 +1,29 @@ import Vue from 'vue'; -import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking'; +import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; describe('MRWidgetChecking', () => { - describe('template', () => { - it('should have correct elements', () => { - const Component = Vue.extend(checkingComponent); - const el = new Component({ - el: document.createElement('div'), - }).$el; + let Component; + let vm; - expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy(); - expect(el.querySelector('button').disabled).toBeTruthy(); - expect(el.innerText).toContain('Checking ability to merge automatically'); - expect(el.querySelector('i')).toBeDefined(); - }); + beforeEach(() => { + Component = Vue.extend(checkingComponent); + vm = mountComponent(Component); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders disabled button', () => { + expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled'); + }); + + it('renders loading icon', () => { + expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner'); + }); + + it('renders information about merging', () => { + expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual('Checking ability to merge automatically'); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js index 1bf97bbf093..51a34739ee9 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js @@ -1,74 +1,58 @@ import Vue from 'vue'; -import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed'; - -const mr = { - targetBranch: 'good-branch', - targetBranchPath: '/good-branch', - metrics: { - mergedBy: {}, - mergedAt: 'mergedUpdatedAt', - closedBy: { - name: 'Fatih Acet', - username: 'fatihacet', - }, - closedAt: 'closedEventUpdatedAt', - readableMergedAt: '', - readableClosedAt: '', - }, - updatedAt: 'mrUpdatedAt', - closedAt: '1 day ago', -}; - -const createComponent = () => { - const Component = Vue.extend(closedComponent); - - return new Component({ - el: document.createElement('div'), - propsData: { mr }, - }); -}; +import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; describe('MRWidgetClosed', () => { - describe('props', () => { - it('should have props', () => { - const mrProp = closedComponent.props.mr; - - expect(mrProp.type instanceof Object).toBeTruthy(); - expect(mrProp.required).toBeTruthy(); - }); + let vm; + + beforeEach(() => { + const Component = Vue.extend(closedComponent); + vm = mountComponent(Component, { mr: { + metrics: { + mergedBy: {}, + closedBy: { + name: 'Administrator', + username: 'root', + webUrl: 'http://localhost:3000/root', + avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + }, + mergedAt: 'Jan 24, 2018 1:02pm GMT+0000', + closedAt: 'Jan 24, 2018 1:02pm GMT+0000', + readableMergedAt: '', + readableClosedAt: 'less than a minute ago', + }, + targetBranchPath: '/twitter/flight/commits/so_long_jquery', + targetBranch: 'so_long_jquery', + } }); }); - describe('components', () => { - it('should have components added', () => { - expect(closedComponent.components['mr-widget-author-and-time']).toBeDefined(); - }); + afterEach(() => { + vm.$destroy(); }); - describe('template', () => { - let vm; - let el; + it('renders warning icon', () => { + expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull(); + }); - beforeEach(() => { - vm = createComponent(); - el = vm.$el; - }); + it('renders closed by information with author and time', () => { + expect( + vm.$el.querySelector('.js-mr-widget-author').textContent.trim().replace(/\s\s+/g, ' '), + ).toContain( + 'Closed by Administrator less than a minute ago', + ); + }); - afterEach(() => { - vm.$destroy(); - }); + it('links to the user that closed the MR', () => { + expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual('http://localhost:3000/root'); + }); - it('should have correct elements', () => { - expect(el.querySelector('h4').textContent).toContain('Closed by'); - expect(el.querySelector('h4').textContent).toContain(mr.metrics.closedBy.name); - expect(el.textContent).toContain('The changes were not merged into'); - expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath); - expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch); - }); + it('renders information about the changes not being merged', () => { + expect( + vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' '), + ).toContain('The changes were not merged into so_long_jquery'); + }); - it('should use closedEvent updatedAt as tooltip title', () => { - expect( - el.querySelector('time').getAttribute('title'), - ).toBe('closedEventUpdatedAt'); - }); + it('renders link for target branch', () => { + expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual('/twitter/flight/commits/so_long_jquery'); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index 5d4c7ec09dc..a7d69fdcdb9 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -1,105 +1,85 @@ import Vue from 'vue'; -import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts'; +import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue'; import mountComponent from '../../../helpers/vue_mount_component_helper'; -const ConflictsComponent = Vue.extend(conflictsComponent); -const path = '/conflicts'; - describe('MRWidgetConflicts', () => { - describe('props', () => { - it('should have props', () => { - const { mr } = conflictsComponent.props; + let Component; + let vm; + const path = '/conflicts'; - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - }); + beforeEach(() => { + Component = Vue.extend(conflictsComponent); }); - describe('template', () => { - describe('when allowed to merge', () => { - let vm; - - beforeEach(() => { - vm = mountComponent(ConflictsComponent, { - mr: { - canMerge: true, - conflictResolutionPath: path, - }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should tell you about conflicts without bothering other people', () => { - expect(vm.$el.textContent).toContain('There are merge conflicts'); - expect(vm.$el.textContent).not.toContain('ask someone with write access'); - }); - - it('should allow you to resolve the conflicts', () => { - const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button'); + afterEach(() => { + vm.$destroy(); + }); - expect(resolveButton.textContent).toContain('Resolve conflicts'); - expect(resolveButton.getAttribute('href')).toEqual(path); + describe('when allowed to merge', () => { + beforeEach(() => { + vm = mountComponent(Component, { + mr: { + canMerge: true, + conflictResolutionPath: path, + }, }); + }); - it('should have merge buttons', () => { - const mergeButton = vm.$el.querySelector('.js-disabled-merge-button'); - const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button'); - - expect(mergeButton.textContent).toContain('Merge'); - expect(mergeButton.disabled).toBeTruthy(); - expect(mergeButton.classList.contains('btn-success')).toEqual(true); - expect(mergeLocallyButton.textContent).toContain('Merge locally'); - }); + it('should tell you about conflicts without bothering other people', () => { + expect(vm.$el.textContent).toContain('There are merge conflicts'); + expect(vm.$el.textContent).not.toContain('ask someone with write access'); }); - describe('when user does not have permission to merge', () => { - let vm; + it('should allow you to resolve the conflicts', () => { + const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button'); - beforeEach(() => { - vm = mountComponent(ConflictsComponent, { - mr: { - canMerge: false, - }, - }); - }); + expect(resolveButton.textContent).toContain('Resolve conflicts'); + expect(resolveButton.getAttribute('href')).toEqual(path); + }); - afterEach(() => { - vm.$destroy(); - }); + it('should have merge buttons', () => { + const mergeButton = vm.$el.querySelector('.js-disabled-merge-button'); + const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button'); - it('should show proper message', () => { - expect(vm.$el.textContent).toContain('ask someone with write access'); - }); + expect(mergeButton.textContent).toContain('Merge'); + expect(mergeButton.disabled).toBeTruthy(); + expect(mergeButton.classList.contains('btn-success')).toEqual(true); + expect(mergeLocallyButton.textContent).toContain('Merge locally'); + }); + }); - it('should not have action buttons', () => { - expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined(); - expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull(); - expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull(); + describe('when user does not have permission to merge', () => { + beforeEach(() => { + vm = mountComponent(Component, { + mr: { + canMerge: false, + }, }); }); - describe('when fast-forward or semi-linear merge enabled', () => { - let vm; + it('should show proper message', () => { + expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('ask someone with write access'); + }); - beforeEach(() => { - vm = mountComponent(ConflictsComponent, { - mr: { - shouldBeRebased: true, - }, - }); - }); + it('should not have action buttons', () => { + expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined(); + expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull(); + expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull(); + }); + }); - afterEach(() => { - vm.$destroy(); + describe('when fast-forward or semi-linear merge enabled', () => { + beforeEach(() => { + vm = mountComponent(Component, { + mr: { + shouldBeRebased: true, + }, }); + }); - it('should tell you to rebase locally', () => { - expect(vm.$el.textContent).toContain('Fast-forward merge is not possible.'); - expect(vm.$el.textContent).toContain('To merge this request, first rebase locally'); - }); + it('should tell you to rebase locally', () => { + expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('Fast-forward merge is not possible.'); + expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('To merge this request, first rebase locally'); }); }); }); -- cgit v1.2.1 From cc02eeff6eca6ad318c5147848dcbc4ddf87680d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 23:21:59 +0800 Subject: Move initialize method later. --- qa/qa/runtime/rsa_key.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qa/qa/runtime/rsa_key.rb b/qa/qa/runtime/rsa_key.rb index e9b6ea66dbd..d456062bce7 100644 --- a/qa/qa/runtime/rsa_key.rb +++ b/qa/qa/runtime/rsa_key.rb @@ -6,13 +6,13 @@ module QA class RSAKey extend Forwardable + attr_reader :key + def_delegators :@key, :fingerprint + def initialize(bits = 4096) @key = OpenSSL::PKey::RSA.new(bits) end - attr_reader :key - def_delegators :@key, :fingerprint - def public_key @public_key ||= "#{key.ssh_type} #{[key.to_blob].pack('m0')}" end -- cgit v1.2.1 From a17f4c23b706004c36242d6455c64f191e5082e5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 23:31:44 +0800 Subject: Add an test for QA::Runtime::RSAKey --- qa/spec/runtime/rsa_key.rb | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 qa/spec/runtime/rsa_key.rb diff --git a/qa/spec/runtime/rsa_key.rb b/qa/spec/runtime/rsa_key.rb new file mode 100644 index 00000000000..ff277b9077b --- /dev/null +++ b/qa/spec/runtime/rsa_key.rb @@ -0,0 +1,9 @@ +describe QA::Runtime::RSAKey do + describe '#public_key' do + subject { described_class.new.public_key } + + it 'generates a public RSA key' do + expect(subject).to match(/\Assh\-rsa AAAA[0-9A-Za-z+\/]+={0,3}\z/) + end + end +end -- cgit v1.2.1 From febf229b91b8336fb153e0bda0e0820d2eb74044 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 24 Jan 2018 15:34:20 +0000 Subject: Uses sprite icon to render verified commit badge and uniforms css colors --- app/assets/stylesheets/pages/commits.scss | 8 ++++---- app/views/projects/commit/_other_user_signature_badge.html.haml | 2 +- .../commit/_same_user_different_email_signature_badge.html.haml | 2 +- app/views/projects/commit/_signature_badge.html.haml | 2 +- app/views/projects/commit/_unverified_signature_badge.html.haml | 2 +- app/views/projects/commit/_verified_signature_badge.html.haml | 2 +- app/views/shared/icons/_icon_status_notfound_borderless.svg | 1 - app/views/shared/icons/_icon_status_success_borderless.svg | 1 - changelogs/unreleased/42285-not-found-status-icon.yml | 5 +++++ 9 files changed, 14 insertions(+), 11 deletions(-) delete mode 100644 app/views/shared/icons/_icon_status_notfound_borderless.svg delete mode 100644 app/views/shared/icons/_icon_status_success_borderless.svg create mode 100644 changelogs/unreleased/42285-not-found-status-icon.yml diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 0cba223c6a6..aeaa33bd3bd 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -310,8 +310,8 @@ } &.invalid { - @include status-color($gray-dark, $gray, $common-gray-dark); - border-color: $common-gray-light; + @include status-color($gray-dark, $gray, $gray-darkest); + border-color: $gray-darkest; } } @@ -335,8 +335,8 @@ &.invalid { svg { - border: 1px solid $common-gray-light; - fill: $common-gray-light; + border: 1px solid $gray-darkest; + fill: $gray-darkest; } } diff --git a/app/views/projects/commit/_other_user_signature_badge.html.haml b/app/views/projects/commit/_other_user_signature_badge.html.haml index 80eca96f7ce..d7bf2dc0cb6 100644 --- a/app/views/projects/commit/_other_user_signature_badge.html.haml +++ b/app/views/projects/commit/_other_user_signature_badge.html.haml @@ -1,6 +1,6 @@ - title = capture do This commit was signed with a different user's verified signature. -- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'icon_status_notfound_borderless', show_user: true } +- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'status_notfound_borderless', show_user: true } = render partial: 'projects/commit/signature_badge', locals: locals diff --git a/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml b/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml index e737de48e22..22ffd66ff8e 100644 --- a/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml +++ b/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml @@ -2,6 +2,6 @@ This commit was signed with a verified signature, but the committer email is not verified to belong to the same user. -- locals = { signature: signature, title: title, label: 'Unverified', css_class: ['invalid'], icon: 'icon_status_notfound_borderless', show_user: true } +- locals = { signature: signature, title: title, label: 'Unverified', css_class: ['invalid'], icon: 'status_notfound_borderless', show_user: true } = render partial: 'projects/commit/signature_badge', locals: locals diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml index 44aa8002f12..aac020b42c5 100644 --- a/app/views/projects/commit/_signature_badge.html.haml +++ b/app/views/projects/commit/_signature_badge.html.haml @@ -10,7 +10,7 @@ - title = capture do .gpg-popover-status .gpg-popover-icon{ class: css_class } - = render "shared/icons/#{icon}.svg" + = sprite_icon(icon) %div = title diff --git a/app/views/projects/commit/_unverified_signature_badge.html.haml b/app/views/projects/commit/_unverified_signature_badge.html.haml index 1af58027b83..00e1efe0582 100644 --- a/app/views/projects/commit/_unverified_signature_badge.html.haml +++ b/app/views/projects/commit/_unverified_signature_badge.html.haml @@ -1,6 +1,6 @@ - title = capture do This commit was signed with an unverified signature. -- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'icon_status_notfound_borderless' } +- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'status_notfound_borderless' } = render partial: 'projects/commit/signature_badge', locals: locals diff --git a/app/views/projects/commit/_verified_signature_badge.html.haml b/app/views/projects/commit/_verified_signature_badge.html.haml index 423beba2120..31408806be7 100644 --- a/app/views/projects/commit/_verified_signature_badge.html.haml +++ b/app/views/projects/commit/_verified_signature_badge.html.haml @@ -2,6 +2,6 @@ This commit was signed with a verified signature and the committer email is verified to belong to the same user. -- locals = { signature: signature, title: title, label: 'Verified', css_class: 'valid', icon: 'icon_status_success_borderless', show_user: true } +- locals = { signature: signature, title: title, label: 'Verified', css_class: 'valid', icon: 'status_success_borderless', show_user: true } = render partial: 'projects/commit/signature_badge', locals: locals diff --git a/app/views/shared/icons/_icon_status_notfound_borderless.svg b/app/views/shared/icons/_icon_status_notfound_borderless.svg deleted file mode 100644 index e58bd264ef8..00000000000 --- a/app/views/shared/icons/_icon_status_notfound_borderless.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/shared/icons/_icon_status_success_borderless.svg b/app/views/shared/icons/_icon_status_success_borderless.svg deleted file mode 100644 index 8ee5be7ab78..00000000000 --- a/app/views/shared/icons/_icon_status_success_borderless.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/changelogs/unreleased/42285-not-found-status-icon.yml b/changelogs/unreleased/42285-not-found-status-icon.yml new file mode 100644 index 00000000000..ea7ff9d6ae7 --- /dev/null +++ b/changelogs/unreleased/42285-not-found-status-icon.yml @@ -0,0 +1,5 @@ +--- +title: Replace verified badge icons and uniform colors +merge_request: +author: +type: fixed -- cgit v1.2.1 From 5e911c5d2c18b5f909f2072cc97bb75704496ea1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 23:35:17 +0800 Subject: Prefer local variables instead --- qa/qa/specs/features/project/add_deploy_key_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb index 67d302a5593..b9998dda895 100644 --- a/qa/qa/specs/features/project/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb @@ -1,13 +1,13 @@ module QA feature 'deploy keys support', :core do - given(:key) { Runtime::RSAKey.new } - given(:deploy_key_title) { 'deploy key title' } - given(:deploy_key_value) { key.public_key } - scenario 'user adds a deploy key' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } + key = Runtime::RSAKey.new + deploy_key_title = 'deploy key title' + deploy_key_value = key.public_key + deploy_key = Factory::Resource::DeployKey.fabricate! do |resource| resource.title = deploy_key_title resource.key = deploy_key_value -- cgit v1.2.1 From c0d7dd38f918dc1ee1975d9bf5e2b61e90a87cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 24 Jan 2018 14:48:02 +0100 Subject: Workaround a recaptcha pop-up that cannot be tested MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/issues/spam_issues_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index 53706ef84bc..c7cfd01f588 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -34,6 +34,9 @@ describe 'New issue', :js do click_button 'Submit issue' + # reCAPTCHA alerts when it can't contact the server, so just accept it and move on + page.driver.browser.switch_to.alert.accept + # it is impossible to test recaptcha automatically and there is no possibility to fill in recaptcha # recaptcha verification is skipped in test environment and it always returns true expect(page).not_to have_content('issue title') -- cgit v1.2.1 From 8f6f421643c02cbc4620269f15f51942b389d9cd Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 24 Jan 2018 16:54:06 +0000 Subject: Moves status icon into a vue file and adds tests Moves merging component into a vue file, adds i18n and better test cases --- .../components/mr_widget_deployment.js | 2 +- .../components/mr_widget_status_icon.js | 36 -------------- .../components/mr_widget_status_icon.vue | 57 ++++++++++++++++++++++ .../components/states/mr_widget_archived.vue | 2 +- .../states/mr_widget_auto_merge_failed.vue | 2 +- .../components/states/mr_widget_checking.js | 2 +- .../components/states/mr_widget_closed.js | 2 +- .../components/states/mr_widget_conflicts.js | 2 +- .../components/states/mr_widget_failed_to_merge.js | 2 +- .../mr_widget_merge_when_pipeline_succeeds.js | 2 +- .../components/states/mr_widget_merged.js | 2 +- .../components/states/mr_widget_merging.js | 29 ----------- .../components/states/mr_widget_merging.vue | 35 +++++++++++++ .../components/states/mr_widget_missing_branch.js | 2 +- .../components/states/mr_widget_not_allowed.js | 2 +- .../states/mr_widget_pipeline_blocked.js | 2 +- .../components/states/mr_widget_pipeline_failed.js | 2 +- .../components/states/mr_widget_ready_to_merge.js | 2 +- .../components/states/mr_widget_rebase.vue | 2 +- .../components/states/mr_widget_sha_mismatch.js | 2 +- .../states/mr_widget_unresolved_discussions.js | 2 +- .../components/states/mr_widget_wip.js | 2 +- .../vue_merge_request_widget/dependencies.js | 2 +- .../components/mr_widget_status_icon_spec.js | 44 +++++++++++++++++ .../components/states/mr_widget_locked_spec.js | 33 ------------- .../components/states/mr_widget_merging_spec.js | 34 +++++++++++++ 26 files changed, 189 insertions(+), 117 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue create mode 100644 spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js delete mode 100644 spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js create mode 100644 spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js index d48f3a01420..d174a900f63 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js @@ -2,7 +2,7 @@ import { getTimeago } from '~/lib/utils/datetime_utility'; import { visitUrl } from '../../lib/utils/url_utility'; import Flash from '../../flash'; import MemoryUsage from './mr_widget_memory_usage'; -import StatusIcon from './mr_widget_status_icon'; +import StatusIcon from './mr_widget_status_icon.vue'; import MRWidgetService from '../services/mr_widget_service'; export default { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js deleted file mode 100644 index eeb990908f6..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.js +++ /dev/null @@ -1,36 +0,0 @@ -import ciIcon from '../../vue_shared/components/ci_icon.vue'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - -export default { - props: { - status: { type: String, required: true }, - showDisabledButton: { type: Boolean, required: false }, - }, - components: { - ciIcon, - loadingIcon, - }, - computed: { - statusObj() { - return { - group: this.status, - icon: `status_${this.status}`, - }; - }, - }, - template: ` -
-
- -
- - -
- `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue new file mode 100644 index 00000000000..1fdc3218671 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -0,0 +1,57 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue index afa9cc57544..cfbd44d41b2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue @@ -1,5 +1,5 @@ + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js index 16ff1109e3f..303877d6fbf 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.js @@ -1,4 +1,4 @@ -import statusIcon from '../mr_widget_status_icon'; +import statusIcon from '../mr_widget_status_icon.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; import mrWidgetMergeHelp from '../../components/mr_widget_merge_help'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js index 00047718201..cea3d97fa88 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.js @@ -1,4 +1,4 @@ -import statusIcon from '../mr_widget_status_icon'; +import statusIcon from '../mr_widget_status_icon.vue'; export default { name: 'MRWidgetNotAllowed', diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js index 2c84f423ee2..e66ce071ab4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.js @@ -1,4 +1,4 @@ -import statusIcon from '../mr_widget_status_icon'; +import statusIcon from '../mr_widget_status_icon.vue'; export default { name: 'MRWidgetPipelineBlocked', diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js index cbaa73deffa..4d9a2ca530f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_failed.js @@ -1,4 +1,4 @@ -import statusIcon from '../mr_widget_status_icon'; +import statusIcon from '../mr_widget_status_icon.vue'; export default { name: 'MRWidgetPipelineBlocked', diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index e51eef07093..7ba6c29006a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -3,7 +3,7 @@ import warningSvg from 'icons/_icon_status_warning.svg'; import simplePoll from '~/lib/utils/simple_poll'; import MergeRequest from '../../../merge_request'; import Flash from '../../../flash'; -import statusIcon from '../mr_widget_status_icon'; +import statusIcon from '../mr_widget_status_icon.vue'; import eventHub from '../../event_hub'; export default { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index 52dd0245ff0..2968af0d5cb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -1,7 +1,7 @@ + + diff --git a/app/assets/javascripts/pages/milestones/shared/event_hub.js b/app/assets/javascripts/pages/milestones/shared/event_hub.js new file mode 100644 index 00000000000..0948c2e5352 --- /dev/null +++ b/app/assets/javascripts/pages/milestones/shared/event_hub.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); diff --git a/app/assets/javascripts/pages/milestones/shared/index.js b/app/assets/javascripts/pages/milestones/shared/index.js new file mode 100644 index 00000000000..327e2cf569c --- /dev/null +++ b/app/assets/javascripts/pages/milestones/shared/index.js @@ -0,0 +1,88 @@ +import Vue from 'vue'; + +import Translate from '~/vue_shared/translate'; + +import deleteMilestoneModal from './components/delete_milestone_modal.vue'; +import eventHub from './event_hub'; + +export default () => { + Vue.use(Translate); + + const onRequestFinished = ({ milestoneUrl, successful }) => { + const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`); + + if (!successful) { + button.removeAttribute('disabled'); + } + + button.querySelector('.js-loading-icon').classList.add('hidden'); + }; + + const onRequestStarted = (milestoneUrl) => { + const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`); + button.setAttribute('disabled', ''); + button.querySelector('.js-loading-icon').classList.remove('hidden'); + eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished); + }; + + const onDeleteButtonClick = (event) => { + const button = event.currentTarget; + const modalProps = { + milestoneId: parseInt(button.dataset.milestoneId, 10), + milestoneTitle: button.dataset.milestoneTitle, + milestoneUrl: button.dataset.milestoneUrl, + issueCount: parseInt(button.dataset.milestoneIssueCount, 10), + mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10), + }; + eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted); + eventHub.$emit('deleteMilestoneModal.props', modalProps); + }; + + const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button'); + for (let i = 0; i < deleteMilestoneButtons.length; i += 1) { + const button = deleteMilestoneButtons[i]; + button.addEventListener('click', onDeleteButtonClick); + } + + eventHub.$once('deleteMilestoneModal.mounted', () => { + for (let i = 0; i < deleteMilestoneButtons.length; i += 1) { + const button = deleteMilestoneButtons[i]; + button.removeAttribute('disabled'); + } + }); + + return new Vue({ + el: '#delete-milestone-modal', + components: { + deleteMilestoneModal, + }, + data() { + return { + modalProps: { + milestoneId: -1, + milestoneTitle: '', + milestoneUrl: '', + issueCount: -1, + mergeRequestCount: -1, + }, + }; + }, + mounted() { + eventHub.$on('deleteMilestoneModal.props', this.setModalProps); + eventHub.$emit('deleteMilestoneModal.mounted'); + }, + beforeDestroy() { + eventHub.$off('deleteMilestoneModal.props', this.setModalProps); + }, + methods: { + setModalProps(modalProps) { + this.modalProps = modalProps; + }, + }, + render(createElement) { + return createElement(deleteMilestoneModal, { + props: this.modalProps, + }); + }, + }); +}; diff --git a/app/assets/javascripts/pages/milestones/shared/init_milestones_show.js b/app/assets/javascripts/pages/milestones/shared/init_milestones_show.js new file mode 100644 index 00000000000..7aa5be0d5b9 --- /dev/null +++ b/app/assets/javascripts/pages/milestones/shared/init_milestones_show.js @@ -0,0 +1,9 @@ +/* eslint-disable no-new */ + +import Milestone from '~/milestone'; +import Sidebar from '~/right_sidebar'; + +export default () => { + new Milestone(); + new Sidebar(); +}; diff --git a/app/assets/javascripts/pages/projects/milestones/index/index.js b/app/assets/javascripts/pages/projects/milestones/index/index.js new file mode 100644 index 00000000000..8fb4d83d8a3 --- /dev/null +++ b/app/assets/javascripts/pages/projects/milestones/index/index.js @@ -0,0 +1,3 @@ +import milestones from '~/pages/milestones/shared'; + +export default milestones; diff --git a/app/assets/javascripts/pages/projects/milestones/show/index.js b/app/assets/javascripts/pages/projects/milestones/show/index.js index 0c3ce848e3d..35b5c9c2ced 100644 --- a/app/assets/javascripts/pages/projects/milestones/show/index.js +++ b/app/assets/javascripts/pages/projects/milestones/show/index.js @@ -1,3 +1,7 @@ -import initMilestonesShow from '~/pages/init_milestones_show'; +import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show'; +import milestones from '~/pages/milestones/shared'; -export default initMilestonesShow; +export default () => { + initMilestonesShow(); + milestones(); +}; diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 51ae09777fd..32b9894ae04 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -9,6 +9,7 @@ .modal-body { background-color: $modal-body-bg; + line-height: $line-height-base; min-height: $modal-body-height; position: relative; padding: #{3 * $grid-size} #{2 * $grid-size}; diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 0f70efbce40..75b17d05e22 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -83,7 +83,7 @@ class Projects::MilestonesController < Projects::ApplicationController Milestones::DestroyService.new(project, current_user).execute(milestone) respond_to do |format| - format.html { redirect_to namespace_project_milestones_path, status: 302 } + format.html { redirect_to namespace_project_milestones_path, status: 303 } format.js { head :ok } end end diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index fcbf7cb802b..6a7bc4b1888 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -12,6 +12,8 @@ New milestone .milestones + #delete-milestone-modal + %ul.content-list = render @milestones diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 5dd4d2c949c..623c42ba88e 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -35,8 +35,18 @@ - else = link_to 'Reopen milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - = link_to project_milestone_path(@project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do - Delete + %button.js-delete-milestone-button.btn.btn-grouped.btn-danger{ data: { toggle: 'modal', + target: '#delete-milestone-modal', + milestone_id: @milestone.id, + milestone_title: markdown_field(@milestone, :title), + milestone_url: project_milestone_path(@project, @milestone), + milestone_issue_count: @milestone.issues.count, + milestone_merge_request_count: @milestone.merge_requests.count }, + disabled: true } + = _('Delete') + = icon('spin spinner', class: 'js-loading-icon hidden' ) + + #delete-milestone-modal %a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index 50f4901a2dd..e08a49b4e59 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -56,6 +56,13 @@ = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped" - = link_to project_milestone_path(milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do - Delete - + %button.js-delete-milestone-button.btn.btn-xs.btn-grouped.btn-danger{ data: { toggle: 'modal', + target: '#delete-milestone-modal', + milestone_id: milestone.id, + milestone_title: markdown_field(milestone, :title), + milestone_url: project_milestone_path(milestone.project, milestone), + milestone_issue_count: milestone.issues.count, + milestone_merge_request_count: milestone.merge_requests.count }, + disabled: true } + = _('Delete') + = icon('spin spinner', class: 'js-loading-icon hidden' ) diff --git a/changelogs/unreleased/winh-delete-milestone-modal.yml b/changelogs/unreleased/winh-delete-milestone-modal.yml new file mode 100644 index 00000000000..6517fbd5f63 --- /dev/null +++ b/changelogs/unreleased/winh-delete-milestone-modal.yml @@ -0,0 +1,5 @@ +--- +title: Add modal for deleting a milestone +merge_request: 16229 +author: +type: other diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature index 1af05b3c326..d121222308d 100644 --- a/features/project/issues/milestones.feature +++ b/features/project/issues/milestones.feature @@ -18,12 +18,15 @@ Feature: Project Issues Milestones Given I click link "New Milestone" And I submit new milestone "v2.3" Then I should see milestone "v2.3" - Given I click link to remove milestone + Given I click button to remove milestone + And I confirm in modal When I visit project "Shop" activity page Then I should see deleted milestone activity + @javascript Scenario: I delete new milestone - Given I click link to remove milestone + Given I click button to remove milestone + And I confirm in modal And I should see no milestones @javascript diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb index 33a24e8913a..4ce67aa651c 100644 --- a/features/steps/project/issues/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -3,7 +3,6 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps include SharedProject include SharedPaths include SharedMarkdown - include CapybaraHelpers step 'I should see milestone "v2.2"' do milestone = @project.milestones.find_by(title: "v2.2") @@ -65,8 +64,12 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps expect(page).to have_selector('#tab-issues li.issuable-row', count: 4) end - step 'I click link to remove milestone' do - confirm_modal_if_present { click_link 'Delete' } + step 'I click button to remove milestone' do + click_button 'Delete' + end + + step 'I confirm in modal' do + click_button 'Delete milestone' end step 'I should see no milestones' do diff --git a/features/support/capybara_helpers.rb b/features/support/capybara_helpers.rb deleted file mode 100644 index 647f8d087c3..00000000000 --- a/features/support/capybara_helpers.rb +++ /dev/null @@ -1,10 +0,0 @@ -module CapybaraHelpers - def confirm_modal_if_present - if Capybara.current_driver == Capybara.javascript_driver - accept_confirm { yield } - return - end - - yield - end -end diff --git a/spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js b/spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js new file mode 100644 index 00000000000..3cd33a3e900 --- /dev/null +++ b/spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js @@ -0,0 +1,95 @@ +import Vue from 'vue'; + +import axios from '~/lib/utils/axios_utils'; +import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue'; +import eventHub from '~/pages/milestones/shared/event_hub'; +import * as urlUtility from '~/lib/utils/url_utility'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +describe('delete_milestone_modal.vue', () => { + const Component = Vue.extend(deleteMilestoneModal); + const props = { + issueCount: 1, + mergeRequestCount: 2, + milestoneId: 3, + milestoneTitle: 'my milestone title', + milestoneUrl: `${gl.TEST_HOST}/delete_milestone_modal.vue/milestone`, + }; + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('onSubmit', () => { + beforeEach(() => { + vm = mountComponent(Component, props); + spyOn(eventHub, '$emit'); + }); + + it('deletes milestone and redirects to overview page', (done) => { + const responseURL = `${gl.TEST_HOST}/delete_milestone_modal.vue/milestoneOverview`; + spyOn(axios, 'delete').and.callFake((url) => { + expect(url).toBe(props.milestoneUrl); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestStarted', props.milestoneUrl); + eventHub.$emit.calls.reset(); + return Promise.resolve({ + request: { + responseURL, + }, + }); + }); + const redirectSpy = spyOn(urlUtility, 'redirectTo'); + + vm.onSubmit() + .then(() => { + expect(redirectSpy).toHaveBeenCalledWith(responseURL); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', { milestoneUrl: props.milestoneUrl, successful: true }); + }) + .then(done) + .catch(done.fail); + }); + + it('displays error if deleting milestone failed', (done) => { + const dummyError = new Error('deleting milestone failed'); + dummyError.response = { status: 418 }; + spyOn(axios, 'delete').and.callFake((url) => { + expect(url).toBe(props.milestoneUrl); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestStarted', props.milestoneUrl); + eventHub.$emit.calls.reset(); + return Promise.reject(dummyError); + }); + const redirectSpy = spyOn(urlUtility, 'redirectTo'); + + vm.onSubmit() + .catch((error) => { + expect(error).toBe(dummyError); + expect(redirectSpy).not.toHaveBeenCalled(); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', { milestoneUrl: props.milestoneUrl, successful: false }); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('text', () => { + it('contains the issue and milestone count', () => { + vm = mountComponent(Component, props); + const value = vm.text; + + expect(value).toContain('remove it from 1 issue and 2 merge requests'); + }); + + it('contains neither issue nor milestone count', () => { + vm = mountComponent(Component, { ...props, + issueCount: 0, + mergeRequestCount: 0, + }); + + const value = vm.text; + + expect(value).toContain('is not currently used'); + }); + }); +}); -- cgit v1.2.1 From 06a187cf233b00a6e8cf1ddabfd6726365386bd2 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 25 Jan 2018 10:24:52 +0000 Subject: Update missing paths --- .../vue_merge_request_widget/components/states/mr_widget_checking.vue | 2 +- .../vue_merge_request_widget/components/states/mr_widget_closed.vue | 2 +- .../components/states/mr_widget_conflicts.vue | 2 +- .../vue_mr_widget/components/states/mr_widget_merging_spec.js | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue index 04e1766b8c7..caeaac75b45 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue @@ -1,5 +1,5 @@ + diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js deleted file mode 100644 index 357485b9e78..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.js +++ /dev/null @@ -1,124 +0,0 @@ -import Flash from '../../../flash'; -import statusIcon from '../mr_widget_status_icon.vue'; -import MRWidgetAuthor from '../../components/mr_widget_author'; -import eventHub from '../../event_hub'; - -export default { - name: 'MRWidgetMergeWhenPipelineSucceeds', - props: { - mr: { type: Object, required: true }, - service: { type: Object, required: true }, - }, - components: { - 'mr-widget-author': MRWidgetAuthor, - statusIcon, - }, - data() { - return { - isCancellingAutoMerge: false, - isRemovingSourceBranch: false, - }; - }, - computed: { - canRemoveSourceBranch() { - const { shouldRemoveSourceBranch, canRemoveSourceBranch, - mergeUserId, currentUserId } = this.mr; - - return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId; - }, - }, - methods: { - cancelAutomaticMerge() { - this.isCancellingAutoMerge = true; - this.service.cancelAutomaticMerge() - .then(res => res.data) - .then((data) => { - eventHub.$emit('UpdateWidgetData', data); - }) - .catch(() => { - this.isCancellingAutoMerge = false; - new Flash('Something went wrong. Please try again.'); // eslint-disable-line - }); - }, - removeSourceBranch() { - const options = { - sha: this.mr.sha, - merge_when_pipeline_succeeds: true, - should_remove_source_branch: true, - }; - - this.isRemovingSourceBranch = true; - this.service.mergeResource.save(options) - .then(res => res.data) - .then((data) => { - if (data.status === 'merge_when_pipeline_succeeds') { - eventHub.$emit('MRWidgetUpdateRequested'); - } - }) - .catch(() => { - this.isRemovingSourceBranch = false; - new Flash('Something went wrong. Please try again.'); // eslint-disable-line - }); - }, - }, - template: ` -
- -
-

- - Set by - - to be merged automatically when the pipeline succeeds - - -