From a8badbe51b9904a34b49cc734c6371bf9c3e4e7e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 24 Jan 2018 21:03:21 -0600 Subject: Replace $.post in label manager with axios --- app/assets/javascripts/label_manager.js | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index ac2f636df0f..5dd517c5af7 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -1,7 +1,8 @@ /* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */ import Sortable from 'vendor/Sortable'; -import Flash from './flash'; +import flash from './flash'; +import axios from './lib/utils/axios_utils'; export default class LabelManager { constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { @@ -50,11 +51,12 @@ export default class LabelManager { if (persistState == null) { persistState = true; } - let xhr; const _this = this; const url = $label.find('.js-toggle-priority').data('url'); let $target = this.prioritizedLabels; let $from = this.otherLabels; + const rollbackLabelPosition = this.rollbackLabelPosition.bind(this, $label, action); + if (action === 'remove') { $target = this.otherLabels; $from = this.prioritizedLabels; @@ -71,40 +73,34 @@ export default class LabelManager { return; } if (action === 'remove') { - xhr = $.ajax({ - url, - type: 'DELETE' - }); + axios.delete(url) + .catch(rollbackLabelPosition); + // Restore empty message if (!$from.find('li').length) { $from.find('.empty-message').removeClass('hidden'); } } else { - xhr = this.savePrioritySort($label, action); + this.savePrioritySort($label, action) + .catch(rollbackLabelPosition); } - return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); } onPrioritySortUpdate() { - const xhr = this.savePrioritySort(); - return xhr.fail(function() { - return new Flash(this.errorMessage, 'alert'); - }); + this.savePrioritySort() + .catch(() => flash(this.errorMessage)); } savePrioritySort() { - return $.post({ - url: this.prioritizedLabels.data('url'), - data: { - label_ids: this.getSortedLabelsIds() - } + return axios.post(this.prioritizedLabels.data('url'), { + label_ids: this.getSortedLabelsIds(), }); } rollbackLabelPosition($label, originalAction) { const action = originalAction === 'remove' ? 'add' : 'remove'; this.toggleLabelPriority($label, action, false); - return new Flash(this.errorMessage, 'alert'); + return flash(this.errorMessage); } getSortedLabelsIds() { -- cgit v1.2.1 From 2c96dd21e1587d23192e69898e75f538890aa8cc Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 24 Jan 2018 21:04:55 -0600 Subject: Remove unnecessary return --- app/assets/javascripts/label_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index 5dd517c5af7..61b40f79db1 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -100,7 +100,7 @@ export default class LabelManager { rollbackLabelPosition($label, originalAction) { const action = originalAction === 'remove' ? 'add' : 'remove'; this.toggleLabelPriority($label, action, false); - return flash(this.errorMessage); + flash(this.errorMessage); } getSortedLabelsIds() { -- cgit v1.2.1 From 54ca8d0d6c4744be53c7044b9bbfa05acc358090 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 23 Jan 2018 20:12:51 +0800 Subject: Fail static-analysis if there's output to stderr TODO: fix offenders --- lib/gitlab/popen.rb | 23 ++++- lib/gitlab/popen/runner.rb | 46 +++++++++ scripts/static-analysis | 57 ++++++++---- spec/lib/gitlab/popen/runner_spec.rb | 139 ++++++++++++++++++++++++++++ spec/lib/gitlab/popen_spec.rb | 16 +++- spec/support/javascript_fixtures_helpers.rb | 1 - 6 files changed, 258 insertions(+), 24 deletions(-) create mode 100644 lib/gitlab/popen/runner.rb create mode 100644 spec/lib/gitlab/popen/runner_spec.rb diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index 4bc5cda8cb5..05526a9ac94 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -5,7 +5,17 @@ module Gitlab module Popen extend self - def popen(cmd, path = nil, vars = {}) + Result = Struct.new(:cmd, :stdout, :stderr, :status, :duration) + + # Returns [stdout + stderr, status] + def popen(cmd, path = nil, vars = {}, &block) + result = popen_with_detail(cmd, path, vars, &block) + + [result.stdout << result.stderr, result.status] + end + + # Returns Result + def popen_with_detail(cmd, path = nil, vars = {}) unless cmd.is_a?(Array) raise "System commands must be given as an array of strings" end @@ -18,18 +28,21 @@ module Gitlab FileUtils.mkdir_p(path) end - cmd_output = "" + cmd_stdout = '' + cmd_stderr = '' cmd_status = 0 + start = Time.now + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| yield(stdin) if block_given? stdin.close - cmd_output << stdout.read - cmd_output << stderr.read + cmd_stdout = stdout.read + cmd_stderr = stderr.read cmd_status = wait_thr.value.exitstatus end - [cmd_output, cmd_status] + Result.new(cmd, cmd_stdout, cmd_stderr, cmd_status, Time.now - start) end end end diff --git a/lib/gitlab/popen/runner.rb b/lib/gitlab/popen/runner.rb new file mode 100644 index 00000000000..36284134707 --- /dev/null +++ b/lib/gitlab/popen/runner.rb @@ -0,0 +1,46 @@ +module Gitlab + module Popen + class Runner + attr_reader :results + + def initialize + @results = [] + end + + def run(commands, &block) + commands.each do |cmd| + # yield doesn't support blocks, so we need to use a block variable + block.call(cmd) do # rubocop:disable Performance/RedundantBlockCall + cmd_result = Gitlab::Popen.popen_with_detail(cmd) + + results << cmd_result + + cmd_result + end + end + end + + def all_good? + all_status_zero? && all_stderr_empty? + end + + def all_status_zero? + results.all? { |result| result.status.zero? } + end + + def all_stderr_empty? + results.all? { |result| result.stderr.empty? } + end + + def failed_results + results.select { |result| result.status.nonzero? } + end + + def warned_results + results.select do |result| + result.status.zero? && !result.stderr.empty? + end + end + end + end +end diff --git a/scripts/static-analysis b/scripts/static-analysis index 96d08287ded..392dc784945 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -1,6 +1,30 @@ #!/usr/bin/env ruby -require ::File.expand_path('../lib/gitlab/popen', __dir__) +# We don't have auto-loading here +require_relative '../lib/gitlab/popen' +require_relative '../lib/gitlab/popen/runner' + +def emit_warnings(static_analysis) + static_analysis.warned_results.each do |result| + puts + puts "**** #{result.cmd.join(' ')} had the following warnings:" + puts + puts result.stdout + puts result.stderr + puts + end +end + +def emit_errors(static_analysis) + static_analysis.failed_results.each do |result| + puts + puts "**** #{result.cmd.join(' ')} failed with the following error:" + puts + puts result.stdout + puts result.stderr + puts + end +end tasks = [ %w[bundle exec rake config_lint], @@ -17,18 +41,16 @@ tasks = [ %w[scripts/lint-rugged] ] -failed_tasks = tasks.reduce({}) do |failures, task| - start = Time.now - puts - puts "$ #{task.join(' ')}" +static_analysis = Gitlab::Popen::Runner.new - output, status = Gitlab::Popen.popen(task) - puts "==> Finished in #{Time.now - start} seconds" +static_analysis.run(tasks) do |cmd, &run| puts + puts "$ #{cmd.join(' ')}" - failures[task.join(' ')] = output unless status.zero? + result = run.call - failures + puts "==> Finished in #{result.duration} seconds" + puts end puts @@ -36,17 +58,20 @@ puts '===================================================' puts puts -if failed_tasks.empty? +if static_analysis.all_good? puts 'All static analyses passed successfully.' +elsif static_analysis.all_status_zero? + puts 'All static analyses passed successfully, but we have warnings:' + puts + + emit_warnings(static_analysis) + + exit 2 else puts 'Some static analyses failed:' - failed_tasks.each do |failed_task, output| - puts - puts "**** #{failed_task} failed with the following error:" - puts - puts output - end + emit_warnings(static_analysis) + emit_errors(static_analysis) exit 1 end diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb new file mode 100644 index 00000000000..e0450b96e0f --- /dev/null +++ b/spec/lib/gitlab/popen/runner_spec.rb @@ -0,0 +1,139 @@ +require 'spec_helper' + +describe Gitlab::Popen::Runner do + subject { described_class.new } + + describe '#run' do + it 'runs the command and returns the result' do + run_command + + expect(Gitlab::Popen).to have_received(:popen_with_detail) + end + end + + describe '#all_good?' do + it 'returns true when exit status is 0 and stderr is empty' do + run_command + + expect(subject).to be_all_good + end + + it 'returns false when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject).not_to be_all_good + end + + it 'returns false when exit stderr has something' do + run_command(stderr: 'stderr') + + expect(subject).not_to be_all_good + end + end + + describe '#all_status_zero?' do + it 'returns true when exit status is 0' do + run_command + + expect(subject).to be_all_status_zero + end + + it 'returns false when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject).not_to be_all_status_zero + end + + it 'returns true' do + run_command(stderr: 'stderr') + + expect(subject).to be_all_status_zero + end + end + + describe '#all_stderr_empty?' do + it 'returns true when stderr is empty' do + run_command + + expect(subject).to be_all_stderr_empty + end + + it 'returns true when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject).to be_all_stderr_empty + end + + it 'returns false when exit stderr has something' do + run_command(stderr: 'stderr') + + expect(subject).not_to be_all_stderr_empty + end + end + + describe '#failed_results' do + it 'returns [] when everything is passed' do + run_command + + expect(subject.failed_results).to be_empty + end + + it 'returns the result when exit status is not 0' do + result = run_command(exitstatus: 1) + + expect(subject.failed_results).to contain_exactly(result) + end + + it 'returns [] when exit stderr has something' do + run_command(stderr: 'stderr') + + expect(subject.failed_results).to be_empty + end + end + + describe '#warned_results' do + it 'returns [] when everything is passed' do + run_command + + expect(subject.warned_results).to be_empty + end + + it 'returns [] when exit status is not 0' do + run_command(exitstatus: 1) + + expect(subject.warned_results).to be_empty + end + + it 'returns the result when exit stderr has something' do + result = run_command(stderr: 'stderr') + + expect(subject.warned_results).to contain_exactly(result) + end + end + + def run_command( + command: 'command', + stdout: 'stdout', + stderr: '', + exitstatus: 0, + status: double(exitstatus: exitstatus, success?: exitstatus.zero?), + duration: 0.1) + + result = + Gitlab::Popen::Result.new(command, stdout, stderr, status, duration) + + allow(Gitlab::Popen) + .to receive(:popen_with_detail) + .and_return(result) + + subject.run([command]) do |cmd, &run| + expect(cmd).to eq(command) + + cmd_result = run.call + + expect(cmd_result).to eq(result) + end + + subject.results.first + end +end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index b145ca36f26..3401c86e540 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -1,11 +1,23 @@ require 'spec_helper' -describe 'Gitlab::Popen' do +describe Gitlab::Popen do let(:path) { Rails.root.join('tmp').to_s } before do @klass = Class.new(Object) - @klass.send(:include, Gitlab::Popen) + @klass.send(:include, described_class) + end + + describe '.popen_with_detail' do + subject { @klass.new.popen_with_detail(cmd) } + + let(:cmd) { %W[#{Gem.ruby} -e $stdout.puts(1);$stderr.puts(2);exit(3)] } + + it { expect(subject.cmd).to eq(cmd) } + it { expect(subject.stdout).to eq("1\n") } + it { expect(subject.stderr).to eq("2\n") } + it { expect(subject.status).to eq(3) } + it { expect(subject.duration).to be_kind_of(Numeric) } end context 'zero status' do diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb index 923c8080e6c..2197bc9d853 100644 --- a/spec/support/javascript_fixtures_helpers.rb +++ b/spec/support/javascript_fixtures_helpers.rb @@ -1,6 +1,5 @@ require 'action_dispatch/testing/test_request' require 'fileutils' -require 'gitlab/popen' module JavaScriptFixturesHelpers include Gitlab::Popen -- cgit v1.2.1 From b226df16a03c7acf956431c33d0574f2e9708256 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 23 Jan 2018 22:39:51 +0800 Subject: Generate secret first to avoid warnings later --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c1d78ef2d48..be18520b876 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -321,6 +321,7 @@ setup-test-env: expire_in: 7d paths: - tmp/tests + - config/secrets.yml rspec-pg 0 27: *rspec-metadata-pg rspec-pg 1 27: *rspec-metadata-pg -- cgit v1.2.1 From df2c47b9ffb65542bb559262758ad952b7ea48a2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 23 Jan 2018 22:45:14 +0800 Subject: Don't print stdout in case we're just printing warnings Otherwise it could be confusing --- scripts/static-analysis | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/static-analysis b/scripts/static-analysis index 392dc784945..8986ad825e1 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -9,7 +9,6 @@ def emit_warnings(static_analysis) puts puts "**** #{result.cmd.join(' ')} had the following warnings:" puts - puts result.stdout puts result.stderr puts end -- cgit v1.2.1 From a65fa2c4e68a42231ec114e002b749c57f83a7de Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 23 Jan 2018 23:02:32 +0800 Subject: Avoid loading rspec-parameterized to avoid warnings for parser which is emitting: ``` warning: parser/current is loading parser/ruby23, which recognizes warning: 2.3.5-compliant syntax, but you are running 2.3.6. warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. ``` There's no easy way to disable this, and we're already using the latest version. This should be harmless anyway. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index ee576c53fe9..05f72b6482f 100644 --- a/Gemfile +++ b/Gemfile @@ -325,7 +325,7 @@ group :development, :test do gem 'spinach-rerun-reporter', '~> 0.0.2' gem 'rspec_profiling', '~> 0.0.5' gem 'rspec-set', '~> 0.1.3' - gem 'rspec-parameterized' + gem 'rspec-parameterized', require: false # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) gem 'minitest', '~> 5.7.0' -- cgit v1.2.1 From b0b6abde1036b0a867310a2cd7cfd72737eb47ab Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 03:03:21 +0800 Subject: Ignore flay warnings --- lib/tasks/flay.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake index 7ad2b2e4d39..b1e012e70c5 100644 --- a/lib/tasks/flay.rake +++ b/lib/tasks/flay.rake @@ -1,6 +1,6 @@ desc 'Code duplication analyze via flay' task :flay do - output = `bundle exec flay --mass 35 app/ lib/gitlab/` + output = `bundle exec flay --mass 35 app/ lib/gitlab/ 2> #{File::NULL}` if output.include? "Similar code found" puts output -- cgit v1.2.1 From cb7974b8f71fc2d36a52f4f0b14b757306950b68 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 03:19:45 +0800 Subject: Convert parser warnings to stdout in haml_lint So we ignore it in static-analysis when status is 0, yet still report it if it's not. --- lib/tasks/haml-lint.rake | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake index ad2d034b0b4..5c0cc4990fc 100644 --- a/lib/tasks/haml-lint.rake +++ b/lib/tasks/haml-lint.rake @@ -2,5 +2,14 @@ unless Rails.env.production? require 'haml_lint/rake_task' require 'haml_lint/inline_javascript' + # Workaround for warnings from parser/current + # TODO: Remove this after we update parser gem + task :haml_lint do + require 'parser' + def Parser.warn(*args) + puts(*args) # static-analysis ignores stdout if status is 0 + end + end + HamlLint::RakeTask.new end -- cgit v1.2.1 From 8e87ecbf3048333034c080b4b49647b12ddec5db Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 16:12:33 +0800 Subject: Eliminate the warnings from task helpers --- lib/gitlab/task_helpers.rb | 170 +++++++++++++++++++++++++++++++++ lib/system_check/helpers.rb | 2 - lib/tasks/gitlab/backup.rake | 36 +++---- lib/tasks/gitlab/check.rake | 24 ++--- lib/tasks/gitlab/cleanup.rake | 8 +- lib/tasks/gitlab/git.rake | 8 +- lib/tasks/gitlab/gitaly.rake | 2 +- lib/tasks/gitlab/helpers.rake | 4 +- lib/tasks/gitlab/info.rake | 2 +- lib/tasks/gitlab/setup.rake | 2 +- lib/tasks/gitlab/shell.rake | 8 +- lib/tasks/gitlab/task_helpers.rb | 170 --------------------------------- lib/tasks/gitlab/workhorse.rake | 2 +- spec/tasks/gitlab/task_helpers_spec.rb | 1 - 14 files changed, 215 insertions(+), 224 deletions(-) create mode 100644 lib/gitlab/task_helpers.rb delete mode 100644 lib/tasks/gitlab/task_helpers.rb diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb new file mode 100644 index 00000000000..c1182af1014 --- /dev/null +++ b/lib/gitlab/task_helpers.rb @@ -0,0 +1,170 @@ +require 'rainbow/ext/string' +require 'gitlab/utils/strong_memoize' + +module Gitlab + TaskFailedError = Class.new(StandardError) + TaskAbortedByUserError = Class.new(StandardError) + + module TaskHelpers + include Gitlab::Utils::StrongMemoize + + extend self + + # Ask if the user wants to continue + # + # Returns "yes" the user chose to continue + # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue + def ask_to_continue + answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no}) + raise Gitlab::TaskAbortedByUserError unless answer == "yes" + end + + # Check which OS is running + # + # It will primarily use lsb_relase to determine the OS. + # It has fallbacks to Debian, SuSE, OS X and systems running systemd. + def os_name + os_name = run_command(%w(lsb_release -irs)) + os_name ||= + if File.readable?('/etc/system-release') + File.read('/etc/system-release') + elsif File.readable?('/etc/debian_version') + "Debian #{File.read('/etc/debian_version')}" + elsif File.readable?('/etc/SuSE-release') + File.read('/etc/SuSE-release') + elsif os_x_version = run_command(%w(sw_vers -productVersion)) + "Mac OS X #{os_x_version}" + elsif File.readable?('/etc/os-release') + File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1] + end + + os_name.try(:squish!) + end + + # Prompt the user to input something + # + # message - the message to display before input + # choices - array of strings of acceptable answers or nil for any answer + # + # Returns the user's answer + def prompt(message, choices = nil) + begin + print(message) + answer = STDIN.gets.chomp + end while choices.present? && !choices.include?(answer) + answer + end + + # Runs the given command and matches the output against the given pattern + # + # Returns nil if nothing matched + # Returns the MatchData if the pattern matched + # + # see also #run_command + # see also String#match + def run_and_match(command, regexp) + run_command(command).try(:match, regexp) + end + + # Runs the given command + # + # Returns '' if the command was not found + # Returns the output of the command otherwise + # + # see also #run_and_match + def run_command(command) + output, _ = Gitlab::Popen.popen(command) + output + rescue Errno::ENOENT + '' # if the command does not exist, return an empty string + end + + # Runs the given command and raises a Gitlab::TaskFailedError exception if + # the command does not exit with 0 + # + # Returns the output of the command otherwise + def run_command!(command) + output, status = Gitlab::Popen.popen(command) + + raise Gitlab::TaskFailedError.new(output) unless status.zero? + + output + end + + def uid_for(user_name) + run_command(%W(id -u #{user_name})).chomp.to_i + end + + def gid_for(group_name) + begin + Etc.getgrnam(group_name).gid + rescue ArgumentError # no group + "group #{group_name} doesn't exist" + end + end + + def gitlab_user + Gitlab.config.gitlab.user + end + + def gitlab_user? + strong_memoize(:is_gitlab_user) do + current_user = run_command(%w(whoami)).chomp + current_user == gitlab_user + end + end + + def warn_user_is_not_gitlab + return if gitlab_user? + + strong_memoize(:warned_user_not_gitlab) do + current_user = run_command(%w(whoami)).chomp + + puts " Warning ".color(:black).background(:yellow) + puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing." + puts " Things may work\/fail for the wrong reasons." + puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}." + puts "" + end + end + + def all_repos + Gitlab.config.repositories.storages.each_value do |repository_storage| + IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -type d -name *.git)) do |find| + find.each_line do |path| + yield path.chomp + end + end + end + end + + def repository_storage_paths_args + Gitlab.config.repositories.storages.values.map { |rs| rs['path'] } + end + + def user_home + Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home + end + + def checkout_or_clone_version(version:, repo:, target_dir:) + version = + if version.starts_with?("=") + version.sub(/\A=/, '') # tag or branch + else + "v#{version}" # tag + end + + clone_repo(repo, target_dir) unless Dir.exist?(target_dir) + checkout_version(version, target_dir) + end + + def clone_repo(repo, target_dir) + run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) + end + + def checkout_version(version, target_dir) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet origin #{version}]) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout -f --quiet FETCH_HEAD --]) + end + end +end diff --git a/lib/system_check/helpers.rb b/lib/system_check/helpers.rb index c42ae4fe4c4..914ed794601 100644 --- a/lib/system_check/helpers.rb +++ b/lib/system_check/helpers.rb @@ -1,5 +1,3 @@ -require 'tasks/gitlab/task_helpers' - module SystemCheck module Helpers include ::Gitlab::TaskHelpers diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 2383bcf954b..24e37f6c6cc 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -4,7 +4,7 @@ namespace :gitlab do namespace :backup do # Create backup of GitLab system desc "GitLab | Create a backup of the GitLab system" - task create: :environment do + task create: :gitlab_environment do warn_user_is_not_gitlab configure_cron_mode @@ -25,7 +25,7 @@ namespace :gitlab do # Restore backup of GitLab system desc 'GitLab | Restore a previously created backup' - task restore: :environment do + task restore: :gitlab_environment do warn_user_is_not_gitlab configure_cron_mode @@ -73,7 +73,7 @@ namespace :gitlab do end namespace :repo do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping repositories ...".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("repositories") @@ -84,7 +84,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring repositories ...".color(:blue) Backup::Repository.new.restore $progress.puts "done".color(:green) @@ -92,7 +92,7 @@ namespace :gitlab do end namespace :db do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping database ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("db") @@ -103,7 +103,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring database ... ".color(:blue) Backup::Database.new.restore $progress.puts "done".color(:green) @@ -111,7 +111,7 @@ namespace :gitlab do end namespace :builds do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping builds ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("builds") @@ -122,7 +122,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring builds ... ".color(:blue) Backup::Builds.new.restore $progress.puts "done".color(:green) @@ -130,7 +130,7 @@ namespace :gitlab do end namespace :uploads do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping uploads ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("uploads") @@ -141,7 +141,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring uploads ... ".color(:blue) Backup::Uploads.new.restore $progress.puts "done".color(:green) @@ -149,7 +149,7 @@ namespace :gitlab do end namespace :artifacts do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping artifacts ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("artifacts") @@ -160,7 +160,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring artifacts ... ".color(:blue) Backup::Artifacts.new.restore $progress.puts "done".color(:green) @@ -168,7 +168,7 @@ namespace :gitlab do end namespace :pages do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping pages ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("pages") @@ -179,7 +179,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring pages ... ".color(:blue) Backup::Pages.new.restore $progress.puts "done".color(:green) @@ -187,7 +187,7 @@ namespace :gitlab do end namespace :lfs do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping lfs objects ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("lfs") @@ -198,7 +198,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring lfs objects ... ".color(:blue) Backup::Lfs.new.restore $progress.puts "done".color(:green) @@ -206,7 +206,7 @@ namespace :gitlab do end namespace :registry do - task create: :environment do + task create: :gitlab_environment do $progress.puts "Dumping container registry images ... ".color(:blue) if Gitlab.config.registry.enabled @@ -221,7 +221,7 @@ namespace :gitlab do end end - task restore: :environment do + task restore: :gitlab_environment do $progress.puts "Restoring container registry images ... ".color(:blue) if Gitlab.config.registry.enabled diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index a584eb97cf5..e05a3aad824 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -1,7 +1,3 @@ -# Temporary hack, until we migrate all checks to SystemCheck format -require 'system_check' -require 'system_check/helpers' - namespace :gitlab do desc 'GitLab | Check the configuration of GitLab and its environment' task check: %w{gitlab:gitlab_shell:check @@ -12,7 +8,7 @@ namespace :gitlab do namespace :app do desc 'GitLab | Check the configuration of the GitLab Rails app' - task check: :environment do + task check: :gitlab_environment do warn_user_is_not_gitlab checks = [ @@ -43,7 +39,7 @@ namespace :gitlab do namespace :gitlab_shell do desc "GitLab | Check the configuration of GitLab Shell" - task check: :environment do + task check: :gitlab_environment do warn_user_is_not_gitlab start_checking "GitLab Shell" @@ -251,7 +247,7 @@ namespace :gitlab do namespace :sidekiq do desc "GitLab | Check the configuration of Sidekiq" - task check: :environment do + task check: :gitlab_environment do warn_user_is_not_gitlab start_checking "Sidekiq" @@ -310,7 +306,7 @@ namespace :gitlab do namespace :incoming_email do desc "GitLab | Check the configuration of Reply by email" - task check: :environment do + task check: :gitlab_environment do warn_user_is_not_gitlab if Gitlab.config.incoming_email.enabled @@ -333,7 +329,7 @@ namespace :gitlab do end namespace :ldap do - task :check, [:limit] => :environment do |_, args| + task :check, [:limit] => :gitlab_environment do |_, args| # Only show up to 100 results because LDAP directories can be very big. # This setting only affects the `rake gitlab:check` script. args.with_defaults(limit: 100) @@ -389,7 +385,7 @@ namespace :gitlab do namespace :repo do desc "GitLab | Check the integrity of the repositories managed by GitLab" - task check: :environment do + task check: :gitlab_environment do puts "This task is deprecated. Please use gitlab:git:fsck instead".color(:red) Rake::Task["gitlab:git:fsck"].execute end @@ -397,7 +393,7 @@ namespace :gitlab do namespace :orphans do desc 'Gitlab | Check for orphaned namespaces and repositories' - task check: :environment do + task check: :gitlab_environment do warn_user_is_not_gitlab checks = [ SystemCheck::Orphans::NamespaceCheck, @@ -408,7 +404,7 @@ namespace :gitlab do end desc 'GitLab | Check for orphaned namespaces in the repositories path' - task check_namespaces: :environment do + task check_namespaces: :gitlab_environment do warn_user_is_not_gitlab checks = [SystemCheck::Orphans::NamespaceCheck] @@ -416,7 +412,7 @@ namespace :gitlab do end desc 'GitLab | Check for orphaned repositories in the repositories path' - task check_repositories: :environment do + task check_repositories: :gitlab_environment do warn_user_is_not_gitlab checks = [SystemCheck::Orphans::RepositoryCheck] @@ -426,7 +422,7 @@ namespace :gitlab do namespace :user do desc "GitLab | Check the integrity of a specific user's repositories" - task :check_repos, [:username] => :environment do |t, args| + task :check_repos, [:username] => :gitlab_environment do |t, args| username = args[:username] || prompt("Check repository integrity for username? ".color(:blue)) user = User.find_by(username: username) if user diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index ab601b0d66b..5a53eac0897 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -5,7 +5,7 @@ namespace :gitlab do HASHED_REPOSITORY_NAME = '@hashed'.freeze desc "GitLab | Cleanup | Clean namespaces" - task dirs: :environment do + task dirs: :gitlab_environment do warn_user_is_not_gitlab remove_flag = ENV['REMOVE'] @@ -49,7 +49,7 @@ namespace :gitlab do end desc "GitLab | Cleanup | Clean repositories" - task repos: :environment do + task repos: :gitlab_environment do warn_user_is_not_gitlab move_suffix = "+orphaned+#{Time.now.to_i}" @@ -78,7 +78,7 @@ namespace :gitlab do end desc "GitLab | Cleanup | Block users that have been removed in LDAP" - task block_removed_ldap_users: :environment do + task block_removed_ldap_users: :gitlab_environment do warn_user_is_not_gitlab block_flag = ENV['BLOCK'] @@ -109,7 +109,7 @@ namespace :gitlab do # released. So likely this should only be run once on gitlab.com # Faulty refs are moved so they are kept around, else some features break. desc 'GitLab | Cleanup | Remove faulty deployment refs' - task move_faulty_deployment_refs: :environment do + task move_faulty_deployment_refs: :gitlab_environment do projects = Project.where(id: Deployment.select(:project_id).distinct) projects.find_each do |project| diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake index 3f5dd2ae3b3..cb4f7e5c8a8 100644 --- a/lib/tasks/gitlab/git.rake +++ b/lib/tasks/gitlab/git.rake @@ -1,7 +1,7 @@ namespace :gitlab do namespace :git do desc "GitLab | Git | Repack" - task repack: :environment do + task repack: :gitlab_environment do failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} repack -a --quiet), "Repacking repo") if failures.empty? puts "Done".color(:green) @@ -11,7 +11,7 @@ namespace :gitlab do end desc "GitLab | Git | Run garbage collection on all repos" - task gc: :environment do + task gc: :gitlab_environment do failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} gc --auto --quiet), "Garbage Collecting") if failures.empty? puts "Done".color(:green) @@ -21,7 +21,7 @@ namespace :gitlab do end desc "GitLab | Git | Prune all repos" - task prune: :environment do + task prune: :gitlab_environment do failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} prune), "Git Prune") if failures.empty? puts "Done".color(:green) @@ -31,7 +31,7 @@ namespace :gitlab do end desc 'GitLab | Git | Check all repos integrity' - task fsck: :environment do + task fsck: :gitlab_environment do failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} fsck --name-objects --no-progress), "Checking integrity") do |repo| check_config_lock(repo) check_ref_locks(repo) diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake index a2e68c0471b..bbe1d3643f1 100644 --- a/lib/tasks/gitlab/gitaly.rake +++ b/lib/tasks/gitlab/gitaly.rake @@ -1,7 +1,7 @@ namespace :gitlab do namespace :gitaly do desc "GitLab | Install or upgrade gitaly" - task :install, [:dir, :repo] => :environment do |t, args| + task :install, [:dir, :repo] => :gitlab_environment do |t, args| require 'toml' warn_user_is_not_gitlab diff --git a/lib/tasks/gitlab/helpers.rake b/lib/tasks/gitlab/helpers.rake index b0a24790c4a..14d1125a03d 100644 --- a/lib/tasks/gitlab/helpers.rake +++ b/lib/tasks/gitlab/helpers.rake @@ -1,8 +1,6 @@ -require 'tasks/gitlab/task_helpers' - # Prevent StateMachine warnings from outputting during a cron task StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON'] -namespace :gitlab do +task gitlab_environment: :environment do extend SystemCheck::Helpers end diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index e9fb6a008b0..45e9a1a1c72 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -1,7 +1,7 @@ namespace :gitlab do namespace :env do desc "GitLab | Show information about GitLab and its environment" - task info: :environment do + task info: :gitlab_environment do # check if there is an RVM environment rvm_version = run_and_match(%w(rvm --version), /[\d\.]+/).try(:to_s) # check Ruby version diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake index 05fcb8e3da5..1d903c81358 100644 --- a/lib/tasks/gitlab/setup.rake +++ b/lib/tasks/gitlab/setup.rake @@ -1,6 +1,6 @@ namespace :gitlab do desc "GitLab | Setup production application" - task setup: :environment do + task setup: :gitlab_environment do setup_db end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 12ae4199b69..844664b12d4 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -1,7 +1,7 @@ namespace :gitlab do namespace :shell do desc "GitLab | Install or upgrade gitlab-shell" - task :install, [:repo] => :environment do |t, args| + task :install, [:repo] => :gitlab_environment do |t, args| warn_user_is_not_gitlab default_version = Gitlab::Shell.version_required @@ -58,12 +58,12 @@ namespace :gitlab do end desc "GitLab | Setup gitlab-shell" - task setup: :environment do + task setup: :gitlab_environment do setup end desc "GitLab | Build missing projects" - task build_missing_projects: :environment do + task build_missing_projects: :gitlab_environment do Project.find_each(batch_size: 1000) do |project| path_to_repo = project.repository.path_to_repo if File.exist?(path_to_repo) @@ -80,7 +80,7 @@ namespace :gitlab do end desc 'Create or repair repository hooks symlink' - task create_hooks: :environment do + task create_hooks: :gitlab_environment do warn_user_is_not_gitlab puts 'Creating/Repairing hooks symlinks for all repositories' diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb deleted file mode 100644 index c1182af1014..00000000000 --- a/lib/tasks/gitlab/task_helpers.rb +++ /dev/null @@ -1,170 +0,0 @@ -require 'rainbow/ext/string' -require 'gitlab/utils/strong_memoize' - -module Gitlab - TaskFailedError = Class.new(StandardError) - TaskAbortedByUserError = Class.new(StandardError) - - module TaskHelpers - include Gitlab::Utils::StrongMemoize - - extend self - - # Ask if the user wants to continue - # - # Returns "yes" the user chose to continue - # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue - def ask_to_continue - answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no}) - raise Gitlab::TaskAbortedByUserError unless answer == "yes" - end - - # Check which OS is running - # - # It will primarily use lsb_relase to determine the OS. - # It has fallbacks to Debian, SuSE, OS X and systems running systemd. - def os_name - os_name = run_command(%w(lsb_release -irs)) - os_name ||= - if File.readable?('/etc/system-release') - File.read('/etc/system-release') - elsif File.readable?('/etc/debian_version') - "Debian #{File.read('/etc/debian_version')}" - elsif File.readable?('/etc/SuSE-release') - File.read('/etc/SuSE-release') - elsif os_x_version = run_command(%w(sw_vers -productVersion)) - "Mac OS X #{os_x_version}" - elsif File.readable?('/etc/os-release') - File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1] - end - - os_name.try(:squish!) - end - - # Prompt the user to input something - # - # message - the message to display before input - # choices - array of strings of acceptable answers or nil for any answer - # - # Returns the user's answer - def prompt(message, choices = nil) - begin - print(message) - answer = STDIN.gets.chomp - end while choices.present? && !choices.include?(answer) - answer - end - - # Runs the given command and matches the output against the given pattern - # - # Returns nil if nothing matched - # Returns the MatchData if the pattern matched - # - # see also #run_command - # see also String#match - def run_and_match(command, regexp) - run_command(command).try(:match, regexp) - end - - # Runs the given command - # - # Returns '' if the command was not found - # Returns the output of the command otherwise - # - # see also #run_and_match - def run_command(command) - output, _ = Gitlab::Popen.popen(command) - output - rescue Errno::ENOENT - '' # if the command does not exist, return an empty string - end - - # Runs the given command and raises a Gitlab::TaskFailedError exception if - # the command does not exit with 0 - # - # Returns the output of the command otherwise - def run_command!(command) - output, status = Gitlab::Popen.popen(command) - - raise Gitlab::TaskFailedError.new(output) unless status.zero? - - output - end - - def uid_for(user_name) - run_command(%W(id -u #{user_name})).chomp.to_i - end - - def gid_for(group_name) - begin - Etc.getgrnam(group_name).gid - rescue ArgumentError # no group - "group #{group_name} doesn't exist" - end - end - - def gitlab_user - Gitlab.config.gitlab.user - end - - def gitlab_user? - strong_memoize(:is_gitlab_user) do - current_user = run_command(%w(whoami)).chomp - current_user == gitlab_user - end - end - - def warn_user_is_not_gitlab - return if gitlab_user? - - strong_memoize(:warned_user_not_gitlab) do - current_user = run_command(%w(whoami)).chomp - - puts " Warning ".color(:black).background(:yellow) - puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing." - puts " Things may work\/fail for the wrong reasons." - puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}." - puts "" - end - end - - def all_repos - Gitlab.config.repositories.storages.each_value do |repository_storage| - IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -type d -name *.git)) do |find| - find.each_line do |path| - yield path.chomp - end - end - end - end - - def repository_storage_paths_args - Gitlab.config.repositories.storages.values.map { |rs| rs['path'] } - end - - def user_home - Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home - end - - def checkout_or_clone_version(version:, repo:, target_dir:) - version = - if version.starts_with?("=") - version.sub(/\A=/, '') # tag or branch - else - "v#{version}" # tag - end - - clone_repo(repo, target_dir) unless Dir.exist?(target_dir) - checkout_version(version, target_dir) - end - - def clone_repo(repo, target_dir) - run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) - end - - def checkout_version(version, target_dir) - run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet origin #{version}]) - run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout -f --quiet FETCH_HEAD --]) - end - end -end diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake index 308ffb0e284..b917a293095 100644 --- a/lib/tasks/gitlab/workhorse.rake +++ b/lib/tasks/gitlab/workhorse.rake @@ -1,7 +1,7 @@ namespace :gitlab do namespace :workhorse do desc "GitLab | Install or upgrade gitlab-workhorse" - task :install, [:dir, :repo] => :environment do |t, args| + task :install, [:dir, :repo] => :gitlab_environment do |t, args| warn_user_is_not_gitlab unless args.dir.present? diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb index fae5ec35c47..e9322ec4931 100644 --- a/spec/tasks/gitlab/task_helpers_spec.rb +++ b/spec/tasks/gitlab/task_helpers_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require 'tasks/gitlab/task_helpers' class TestHelpersTest include Gitlab::TaskHelpers -- cgit v1.2.1 From 1f4c40650b8d587b4ab6dd749fd0c6b6c92dcb49 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 16:16:24 +0800 Subject: Eliminate the warnings for database --- lib/tasks/migrate/setup_postgresql.rake | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index c996537cfbe..31cbd651edb 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -1,16 +1,14 @@ -require Rails.root.join('lib/gitlab/database') -require Rails.root.join('lib/gitlab/database/migration_helpers') -require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes') -require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes') -require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes') -require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like') -require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes') -require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like') -require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb') -require Rails.root.join('db/migrate/20180113220114_rework_redirect_routes_indexes.rb') - desc 'GitLab | Sets up PostgreSQL' task setup_postgresql: :environment do + require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes') + require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes') + require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes') + require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like') + require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes') + require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like') + require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb') + require Rails.root.join('db/migrate/20180113220114_rework_redirect_routes_indexes.rb') + NamespacesProjectsPathLowerIndexes.new.up AddUsersLowerUsernameEmailIndexes.new.up AddLowerPathIndexToRoutes.new.up -- cgit v1.2.1 From 99784d77978e7548f70fa8499c5c9931c507d6f4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 16:35:31 +0800 Subject: We need Rails in order to use Gitlab.config anyway --- bin/upgrade.rb | 2 +- lib/gitlab/upgrader.rb | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/upgrade.rb b/bin/upgrade.rb index a5caecf8526..ed628fa1d42 100755 --- a/bin/upgrade.rb +++ b/bin/upgrade.rb @@ -1,3 +1,3 @@ -require_relative "../lib/gitlab/upgrader" +require_relative '../config/environment' Gitlab::Upgrader.new.execute diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index 3b64cb32afa..d545f2f95f1 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -1,6 +1,3 @@ -require_relative "popen" -require_relative "version_info" - module Gitlab class Upgrader def execute -- cgit v1.2.1 From e042baebb81ebd2b60becd9bebd2d088881aeca4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 17:05:39 +0800 Subject: Eliminate the last warning for redis wrapper --- config/application.rb | 1 + lib/gitlab/redis/cache.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index 2067428ff62..751307de975 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,6 +6,7 @@ Bundler.require(:default, Rails.env) module Gitlab class Application < Rails::Application + require_dependency Rails.root.join('lib/gitlab/redis/wrapper') require_dependency Rails.root.join('lib/gitlab/redis/cache') require_dependency Rails.root.join('lib/gitlab/redis/queues') require_dependency Rails.root.join('lib/gitlab/redis/shared_state') diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb index 9bf019b72e6..a991933e910 100644 --- a/lib/gitlab/redis/cache.rb +++ b/lib/gitlab/redis/cache.rb @@ -1,5 +1,5 @@ # please require all dependencies below: -require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper) +require_relative 'wrapper' unless defined?(::Rails) && ::Rails.root.present? module Gitlab module Redis -- cgit v1.2.1 From 0bf918f05e827b380107d88f4592d1ceedd632f9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 18:01:45 +0800 Subject: Fix rubocop offenses. It's not checked before when it's inside lib/tasks/* --- .rubocop.yml | 4 ++++ lib/gitlab/task_helpers.rb | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 563a00db6c0..4e80b49ccd2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -19,6 +19,10 @@ AllCops: - 'builds/**/*' CacheRootDirectory: tmp +# TODO: Remove me after updating gitlab-style +Style/TrailingUnderscoreVariable: + Enabled: false + # Gitlab ################################################################### Gitlab/ModuleWithInstanceVariables: diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb index c1182af1014..34bee6fecbe 100644 --- a/lib/gitlab/task_helpers.rb +++ b/lib/gitlab/task_helpers.rb @@ -1,6 +1,7 @@ require 'rainbow/ext/string' require 'gitlab/utils/strong_memoize' +# rubocop:disable Rails/Output module Gitlab TaskFailedError = Class.new(StandardError) TaskAbortedByUserError = Class.new(StandardError) @@ -96,11 +97,9 @@ module Gitlab end def gid_for(group_name) - begin - Etc.getgrnam(group_name).gid - rescue ArgumentError # no group - "group #{group_name} doesn't exist" - end + Etc.getgrnam(group_name).gid + rescue ArgumentError # no group + "group #{group_name} doesn't exist" end def gitlab_user -- cgit v1.2.1 From a2618310aea7d58e52d2d29ec4871e27717eb0f0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Jan 2018 21:05:01 +0800 Subject: Use Process::Status rather than an integer However keep backward compatibility --- lib/gitlab/popen.rb | 6 +++--- lib/gitlab/popen/runner.rb | 12 ++++++------ scripts/static-analysis | 4 ++-- spec/lib/gitlab/popen/runner_spec.rb | 16 ++++++++-------- spec/lib/gitlab/popen_spec.rb | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index 05526a9ac94..b9832a724c4 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -11,7 +11,7 @@ module Gitlab def popen(cmd, path = nil, vars = {}, &block) result = popen_with_detail(cmd, path, vars, &block) - [result.stdout << result.stderr, result.status] + [result.stdout << result.stderr, result.status&.exitstatus] end # Returns Result @@ -30,7 +30,7 @@ module Gitlab cmd_stdout = '' cmd_stderr = '' - cmd_status = 0 + cmd_status = nil start = Time.now Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| @@ -39,7 +39,7 @@ module Gitlab cmd_stdout = stdout.read cmd_stderr = stderr.read - cmd_status = wait_thr.value.exitstatus + cmd_status = wait_thr.value end Result.new(cmd, cmd_stdout, cmd_stderr, cmd_status, Time.now - start) diff --git a/lib/gitlab/popen/runner.rb b/lib/gitlab/popen/runner.rb index 36284134707..f44035a48bb 100644 --- a/lib/gitlab/popen/runner.rb +++ b/lib/gitlab/popen/runner.rb @@ -20,12 +20,12 @@ module Gitlab end end - def all_good? - all_status_zero? && all_stderr_empty? + def all_success_and_clean? + all_success? && all_stderr_empty? end - def all_status_zero? - results.all? { |result| result.status.zero? } + def all_success? + results.all? { |result| result.status.success? } end def all_stderr_empty? @@ -33,12 +33,12 @@ module Gitlab end def failed_results - results.select { |result| result.status.nonzero? } + results.reject { |result| result.status.success? } end def warned_results results.select do |result| - result.status.zero? && !result.stderr.empty? + result.status.success? && !result.stderr.empty? end end end diff --git a/scripts/static-analysis b/scripts/static-analysis index 8986ad825e1..b3895292ab4 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -57,9 +57,9 @@ puts '===================================================' puts puts -if static_analysis.all_good? +if static_analysis.all_success_and_clean? puts 'All static analyses passed successfully.' -elsif static_analysis.all_status_zero? +elsif static_analysis.all_success? puts 'All static analyses passed successfully, but we have warnings:' puts diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb index e0450b96e0f..2e2cb4ca28f 100644 --- a/spec/lib/gitlab/popen/runner_spec.rb +++ b/spec/lib/gitlab/popen/runner_spec.rb @@ -11,43 +11,43 @@ describe Gitlab::Popen::Runner do end end - describe '#all_good?' do + describe '#all_success_and_clean?' do it 'returns true when exit status is 0 and stderr is empty' do run_command - expect(subject).to be_all_good + expect(subject).to be_all_success_and_clean end it 'returns false when exit status is not 0' do run_command(exitstatus: 1) - expect(subject).not_to be_all_good + expect(subject).not_to be_all_success_and_clean end it 'returns false when exit stderr has something' do run_command(stderr: 'stderr') - expect(subject).not_to be_all_good + expect(subject).not_to be_all_success_and_clean end end - describe '#all_status_zero?' do + describe '#all_success?' do it 'returns true when exit status is 0' do run_command - expect(subject).to be_all_status_zero + expect(subject).to be_all_success end it 'returns false when exit status is not 0' do run_command(exitstatus: 1) - expect(subject).not_to be_all_status_zero + expect(subject).not_to be_all_success end it 'returns true' do run_command(stderr: 'stderr') - expect(subject).to be_all_status_zero + expect(subject).to be_all_success end end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 3401c86e540..1dbead16d5b 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::Popen do it { expect(subject.cmd).to eq(cmd) } it { expect(subject.stdout).to eq("1\n") } it { expect(subject.stderr).to eq("2\n") } - it { expect(subject.status).to eq(3) } + it { expect(subject.status.exitstatus).to eq(3) } it { expect(subject.duration).to be_kind_of(Numeric) } end -- cgit v1.2.1 From e7115a3e9ccfd45edee3621d3b4f74a851adce30 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 25 Jan 2018 16:08:04 +0800 Subject: Remove bin/upgrade.rb as we don't seem to refer it --- bin/upgrade.rb | 3 --- 1 file changed, 3 deletions(-) delete mode 100755 bin/upgrade.rb diff --git a/bin/upgrade.rb b/bin/upgrade.rb deleted file mode 100755 index ed628fa1d42..00000000000 --- a/bin/upgrade.rb +++ /dev/null @@ -1,3 +0,0 @@ -require_relative '../config/environment' - -Gitlab::Upgrader.new.execute -- cgit v1.2.1 From 02a3f6241af193cabde303de5d1eea0e82c87cc5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 26 Jan 2018 11:51:11 +0000 Subject: Update gitlab-styles and update .rubocop.yml --- .rubocop.yml | 4 ---- Gemfile.lock | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4e80b49ccd2..563a00db6c0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -19,10 +19,6 @@ AllCops: - 'builds/**/*' CacheRootDirectory: tmp -# TODO: Remove me after updating gitlab-style -Style/TrailingUnderscoreVariable: - Enabled: false - # Gitlab ################################################################### Gitlab/ModuleWithInstanceVariables: diff --git a/Gemfile.lock b/Gemfile.lock index 5532888d179..1a3c8f42469 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -304,7 +304,7 @@ GEM mime-types (>= 1.16) posix-spawn (~> 0.3) gitlab-markup (1.6.3) - gitlab-styles (2.3.1) + gitlab-styles (2.3.2) rubocop (~> 0.51) rubocop-gitlab-security (~> 0.1.0) rubocop-rspec (~> 1.19) -- cgit v1.2.1 From 1d246b1bc3755ff09f2117dcef54e572e8facde9 Mon Sep 17 00:00:00 2001 From: Muhamed Date: Fri, 26 Jan 2018 14:24:06 +0000 Subject: Update index.md --- doc/user/project/repository/branches/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md index 26c55891b3c..9d16a4c74f2 100644 --- a/doc/user/project/repository/branches/index.md +++ b/doc/user/project/repository/branches/index.md @@ -18,7 +18,7 @@ When you create a new [project](../../index.md), GitLab sets `master` as the def branch for your project. You can choose another branch to be your project's default under your project's **Settings > General**. -The default branch is the branched affected by the +The default branch is the branch affected by the [issue closing pattern](../../issues/automatic_issue_closing.md), which means that _an issue will be closed when a merge request is merged to the **default branch**_. -- cgit v1.2.1 From ca05c3a9b6a000bb468fac7b61ff08ba51681b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 26 Jan 2018 19:15:38 +0100 Subject: Replace : with _ in cache key versioning --- app/models/ci/build.rb | 2 +- spec/models/ci/build_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index df67fb243ad..6ced5fb0e24 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -466,7 +466,7 @@ module Ci if cache && project.jobs_cache_index cache = cache.merge( - key: "#{cache[:key]}:#{project.jobs_cache_index}") + key: "#{cache[:key]}_#{project.jobs_cache_index}") end [cache] diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 45a606c1ea8..f5b3b4a9fc5 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -277,7 +277,7 @@ describe Ci::Build do allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1) end - it { is_expected.to be_an(Array).and all(include(key: "key:1")) } + it { is_expected.to be_an(Array).and all(include(key: "key_1")) } end context 'when project does not have jobs_cache_index' do -- cgit v1.2.1 From 87c4e874fdffd1139f28be6ba74413b93a253c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 26 Jan 2018 19:15:53 +0100 Subject: Add CHANGELOG entry --- changelogs/unreleased/fix-cache-clear-windows.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/fix-cache-clear-windows.yml diff --git a/changelogs/unreleased/fix-cache-clear-windows.yml b/changelogs/unreleased/fix-cache-clear-windows.yml new file mode 100644 index 00000000000..2be6bac004b --- /dev/null +++ b/changelogs/unreleased/fix-cache-clear-windows.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix cache clear bug withg using : on Windows' +merge_request: 16740 +author: +type: fixed -- cgit v1.2.1 From 5ba03e585b0d228aeafb8374bf929cd1141edba9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 26 Jan 2018 23:09:30 +0000 Subject: Move mr_widget_merged into a vue file --- .../components/states/mr_widget_merged.js | 139 -------------- .../components/states/mr_widget_merged.vue | 192 +++++++++++++++++++ .../vue_merge_request_widget/dependencies.js | 2 +- .../components/states/mr_widget_merged_spec.js | 208 ++++++++++----------- 4 files changed, 290 insertions(+), 251 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js deleted file mode 100644 index 7f8d78cab73..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js +++ /dev/null @@ -1,139 +0,0 @@ -import Flash from '../../../flash'; -import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; -import tooltip from '../../../vue_shared/directives/tooltip'; -import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; -import statusIcon from '../mr_widget_status_icon.vue'; -import eventHub from '../../event_hub'; - -export default { - name: 'MRWidgetMerged', - props: { - mr: { type: Object, required: true }, - service: { type: Object, required: true }, - }, - data() { - return { - isMakingRequest: false, - }; - }, - directives: { - tooltip, - }, - components: { - 'mr-widget-author-and-time': mrWidgetAuthorTime, - loadingIcon, - statusIcon, - }, - computed: { - shouldShowRemoveSourceBranch() { - const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr; - - return !sourceBranchRemoved && canRemoveSourceBranch && - !this.isMakingRequest && !isRemovingSourceBranch; - }, - shouldShowSourceBranchRemoving() { - const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr; - return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest); - }, - shouldShowMergedButtons() { - const { canRevertInCurrentMR, canCherryPickInCurrentMR, revertInForkPath, - cherryPickInForkPath } = this.mr; - - return canRevertInCurrentMR || canCherryPickInCurrentMR || - revertInForkPath || cherryPickInForkPath; - }, - }, - methods: { - removeSourceBranch() { - this.isMakingRequest = true; - this.service.removeSourceBranch() - .then(res => res.data) - .then((data) => { - if (data.message === 'Branch was removed') { - eventHub.$emit('MRWidgetUpdateRequested', () => { - this.isMakingRequest = false; - }); - } - }) - .catch(() => { - this.isMakingRequest = false; - new Flash('Something went wrong. Please try again.'); // eslint-disable-line - }); - }, - }, - template: ` -
- -
- -
-

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

-

The source branch has been removed

-

- You can remove source branch now - -

-

- - The source branch is being removed -

-
-
-
- `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue new file mode 100644 index 00000000000..91bdc87e03f --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -0,0 +1,192 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index b930aca6877..2917090e073 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -16,7 +16,7 @@ export { default as WidgetMergeHelp } from './components/mr_widget_merge_help'; export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue'; 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 MergedState } from './components/states/mr_widget_merged.vue'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue'; export { default as ClosedState } from './components/states/mr_widget_closed.vue'; export { default as MergingState } from './components/states/mr_widget_merging.vue'; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js index 2dc3b72ea40..43a989393ba 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -1,108 +1,99 @@ import Vue from 'vue'; -import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged'; +import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue'; import eventHub from '~/vue_merge_request_widget/event_hub'; - -const targetBranch = 'foo'; - -const createComponent = () => { - const Component = Vue.extend(mergedComponent); - const mr = { - isRemovingSourceBranch: false, - cherryPickInForkPath: false, - canCherryPickInCurrentMR: true, - revertInForkPath: false, - canRevertInCurrentMR: true, - canRemoveSourceBranch: true, - sourceBranchRemoved: true, - metrics: { - mergedBy: {}, - mergedAt: 'mergedUpdatedAt', - readableMergedAt: '', - closedBy: {}, - closedAt: 'mergedUpdatedAt', - readableClosedAt: '', - }, - updatedAt: 'mrUpdatedAt', - targetBranch, - }; - - const service = { - removeSourceBranch() {}, - }; - - return new Component({ - el: document.createElement('div'), - propsData: { mr, service }, - }); -}; +import mountComponent from '../../../helpers/vue_mount_component_helper'; describe('MRWidgetMerged', () => { - describe('props', () => { - it('should have props', () => { - const { mr, service } = mergedComponent.props; - - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - - expect(service.type instanceof Object).toBeTruthy(); - expect(service.required).toBeTruthy(); - }); - }); - - describe('components', () => { - it('should have components added', () => { - expect(mergedComponent.components['mr-widget-author-and-time']).toBeDefined(); - }); + let vm; + const targetBranch = 'foo'; + + beforeEach(() => { + const Component = Vue.extend(mergedComponent); + const mr = { + isRemovingSourceBranch: false, + cherryPickInForkPath: false, + canCherryPickInCurrentMR: true, + revertInForkPath: false, + canRevertInCurrentMR: true, + canRemoveSourceBranch: true, + sourceBranchRemoved: true, + metrics: { + mergedBy: { + 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', + readableMergedAt: '', + closedBy: {}, + closedAt: 'Jan 24, 2018 1:02pm GMT+0000', + readableClosedAt: '', + }, + updatedAt: 'mergedUpdatedAt', + targetBranch, + }; + + const service = { + removeSourceBranch() {}, + }; + + spyOn(eventHub, '$emit'); + + vm = mountComponent(Component, { mr, service }); }); - describe('data', () => { - it('should have default data', () => { - const data = mergedComponent.data(); - - expect(data.isMakingRequest).toBeFalsy(); - }); + afterEach(() => { + vm.$destroy(); }); describe('computed', () => { describe('shouldShowRemoveSourceBranch', () => { - it('should correct value when fields changed', () => { - const vm = createComponent(); + it('returns true when sourceBranchRemoved is false', () => { vm.mr.sourceBranchRemoved = false; - expect(vm.shouldShowRemoveSourceBranch).toBeTruthy(); + expect(vm.shouldShowRemoveSourceBranch).toEqual(true); + }); + it('returns false wehn sourceBranchRemoved is true', () => { vm.mr.sourceBranchRemoved = true; - expect(vm.shouldShowRemoveSourceBranch).toBeFalsy(); + expect(vm.shouldShowRemoveSourceBranch).toEqual(false); + }); + it('returns false when canRemoveSourceBranch is false', () => { vm.mr.sourceBranchRemoved = false; vm.mr.canRemoveSourceBranch = false; - expect(vm.shouldShowRemoveSourceBranch).toBeFalsy(); + expect(vm.shouldShowRemoveSourceBranch).toEqual(false); + }); + it('returns false when is making request', () => { vm.mr.canRemoveSourceBranch = true; vm.isMakingRequest = true; - expect(vm.shouldShowRemoveSourceBranch).toBeFalsy(); + expect(vm.shouldShowRemoveSourceBranch).toEqual(false); + }); + it('returns true when all are true', () => { vm.mr.isRemovingSourceBranch = true; vm.mr.canRemoveSourceBranch = true; vm.isMakingRequest = true; - expect(vm.shouldShowRemoveSourceBranch).toBeFalsy(); + expect(vm.shouldShowRemoveSourceBranch).toEqual(false); }); }); + describe('shouldShowSourceBranchRemoving', () => { it('should correct value when fields changed', () => { - const vm = createComponent(); vm.mr.sourceBranchRemoved = false; - expect(vm.shouldShowSourceBranchRemoving).toBeFalsy(); + expect(vm.shouldShowSourceBranchRemoving).toEqual(false); vm.mr.sourceBranchRemoved = true; - expect(vm.shouldShowRemoveSourceBranch).toBeFalsy(); + expect(vm.shouldShowRemoveSourceBranch).toEqual(false); vm.mr.sourceBranchRemoved = false; vm.isMakingRequest = true; - expect(vm.shouldShowSourceBranchRemoving).toBeTruthy(); + expect(vm.shouldShowSourceBranchRemoving).toEqual(true); vm.isMakingRequest = false; vm.mr.isRemovingSourceBranch = true; - expect(vm.shouldShowSourceBranchRemoving).toBeTruthy(); + expect(vm.shouldShowSourceBranchRemoving).toEqual(true); }); }); }); @@ -110,8 +101,6 @@ describe('MRWidgetMerged', () => { describe('methods', () => { describe('removeSourceBranch', () => { it('should set flag and call service then request main component to update the widget', (done) => { - const vm = createComponent(); - spyOn(eventHub, '$emit'); spyOn(vm.service, 'removeSourceBranch').and.returnValue(new Promise((resolve) => { resolve({ data: { @@ -123,7 +112,7 @@ describe('MRWidgetMerged', () => { vm.removeSourceBranch(); setTimeout(() => { const args = eventHub.$emit.calls.argsFor(0); - expect(vm.isMakingRequest).toBeTruthy(); + expect(vm.isMakingRequest).toEqual(true); expect(args[0]).toEqual('MRWidgetUpdateRequested'); expect(args[1]).not.toThrow(); done(); @@ -132,53 +121,50 @@ describe('MRWidgetMerged', () => { }); }); - describe('template', () => { - let vm; - let el; + it('has merged by information', () => { + expect(vm.$el.textContent).toContain('Merged by'); + expect(vm.$el.textContent).toContain('Administrator'); + }); - beforeEach(() => { - vm = createComponent(); - el = vm.$el; - }); + it('renders branch information', () => { + expect(vm.$el.textContent).toContain('The changes were merged into'); + expect(vm.$el.textContent).toContain(targetBranch); + }); - it('should have correct elements', () => { - expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.querySelector('.js-mr-widget-author')).toBeDefined(); - expect(el.innerText).toContain('The changes were merged into'); - expect(el.innerText).toContain(targetBranch); - expect(el.innerText).toContain('The source branch has been removed'); - expect(el.innerText).toContain('Revert'); - expect(el.innerText).toContain('Cherry-pick'); - expect(el.innerText).not.toContain('You can remove source branch now'); - expect(el.innerText).not.toContain('The source branch is being removed'); - }); + it('renders information about branch being removed', () => { + expect(vm.$el.textContent).toContain('The source branch has been removed'); + }); - it('should not show source branch removed text', (done) => { - vm.mr.sourceBranchRemoved = false; + it('shows revert and cherry-pick buttons', () => { + expect(vm.$el.textContent).toContain('Revert'); + expect(vm.$el.textContent).toContain('Cherry-pick'); + }); - Vue.nextTick(() => { - expect(el.innerText).toContain('You can remove source branch now'); - expect(el.innerText).not.toContain('The source branch has been removed'); - done(); - }); + it('should not show source branch removed text', (done) => { + vm.mr.sourceBranchRemoved = false; + + Vue.nextTick(() => { + expect(vm.$el.innerText).toContain('You can remove source branch now'); + expect(vm.$el.innerText).not.toContain('The source branch has been removed'); + done(); }); + }); - it('should show source branch removing text', (done) => { - vm.mr.isRemovingSourceBranch = true; - vm.mr.sourceBranchRemoved = false; + it('should show source branch removing text', (done) => { + vm.mr.isRemovingSourceBranch = true; + vm.mr.sourceBranchRemoved = false; - Vue.nextTick(() => { - expect(el.innerText).toContain('The source branch is being removed'); - expect(el.innerText).not.toContain('You can remove source branch now'); - expect(el.innerText).not.toContain('The source branch has been removed'); - done(); - }); + Vue.nextTick(() => { + expect(vm.$el.innerText).toContain('The source branch is being removed'); + expect(vm.$el.innerText).not.toContain('You can remove source branch now'); + expect(vm.$el.innerText).not.toContain('The source branch has been removed'); + done(); }); + }); - it('should use mergedEvent updatedAt as tooltip title', () => { - expect( - el.querySelector('time').getAttribute('title'), - ).toBe('mergedUpdatedAt'); - }); + it('should use mergedEvent mergedAt as tooltip title', () => { + expect( + vm.$el.querySelector('time').getAttribute('title'), + ).toBe('Jan 24, 2018 1:02pm GMT+0000'); }); }); -- cgit v1.2.1 From 9132c4d5f7309c828bf0e86f17d5b42030a31d0d Mon Sep 17 00:00:00 2001 From: kasbah Date: Sat, 27 Jan 2018 22:02:49 +0000 Subject: doc: Correct example responses in repository_files.md --- doc/api/repository_files.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index a1a0b1b756c..c29dc22e12d 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -68,7 +68,7 @@ Example response: ```json { - "file_name": "app/project.rb", + "file_path": "app/project.rb", "branch": "master" } ``` @@ -98,7 +98,7 @@ Example response: ```json { - "file_name": "app/project.rb", + "file_path": "app/project.rb", "branch": "master" } ``` @@ -134,15 +134,6 @@ DELETE /projects/:id/repository/files/:file_path curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file' ``` -Example response: - -```json -{ - "file_name": "app/project.rb", - "branch": "master" -} -``` - Parameters: - `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb -- cgit v1.2.1 From 2944896466ea89a334556c19c82b5403ab6b9f32 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 --- .../unreleased/osw-updates-merge-status-on-api-actions.yml | 5 +++++ lib/api/entities.rb | 9 ++++++++- spec/requests/api/merge_requests_spec.rb | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml 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..3854985e576 --- /dev/null +++ b/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml @@ -0,0 +1,5 @@ +--- +title: Return more consistent values for merge_status on MR APIs +merge_request: +author: +type: fixed diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7b9a80a234b..ac5b6e518fd 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -507,7 +507,14 @@ 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| + merge_request.tap(&:check_if_can_be_merged).merge_status + end expose :diff_head_sha, as: :sha expose :merge_commit_sha expose :user_notes_count diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 8e2982f1a5d..14dd9da119d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -198,6 +198,8 @@ describe API::MergeRequests do create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) + create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) + expect do get api("/projects/#{project.id}/merge_requests", user) end.not_to exceed_query_limit(control) -- cgit v1.2.1 From 0dfa63739263dc0d7524a387a40ce08984c444bc Mon Sep 17 00:00:00 2001 From: Jacopo Date: Sun, 28 Jan 2018 21:49:15 +0100 Subject: Adds spacing between edit and delete btn in tag list --- app/views/projects/tags/_tag.html.haml | 2 +- changelogs/unreleased/41802-add-space-to-edit-delete-tag-btns.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/41802-add-space-to-edit-delete-tag-btns.yml diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 467f19b4c56..55e45a5e954 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -32,5 +32,5 @@ = icon("pencil") - if can?(current_user, :admin_project, @project) - = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do + = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do = icon("trash-o") diff --git a/changelogs/unreleased/41802-add-space-to-edit-delete-tag-btns.yml b/changelogs/unreleased/41802-add-space-to-edit-delete-tag-btns.yml new file mode 100644 index 00000000000..f23a6452b0d --- /dev/null +++ b/changelogs/unreleased/41802-add-space-to-edit-delete-tag-btns.yml @@ -0,0 +1,5 @@ +--- +title: Adds spacing between edit and delete tag btn in tag list +merge_request: 16757 +author: Jacopo Beschi @jacopo-beschi +type: fixed -- cgit v1.2.1 From c62ffe27ad5f7072f5e5b44cd5bf635036aa1c3a Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 29 Jan 2018 10:58:41 +0000 Subject: Fix broken test --- .../vue_merge_request_widget/components/states/mr_widget_merged.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index 91bdc87e03f..a92e0b3c124 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -113,7 +113,7 @@ :date-title="mr.metrics.mergedAt" :date-readable="mr.metrics.readableMergedAt" /> - + Date: Mon, 29 Jan 2018 12:05:04 +0000 Subject: Converted gl_dropdown to axios --- app/assets/javascripts/gl_dropdown.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 64f258aed64..15df7a7f989 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -2,6 +2,7 @@ /* global fuzzaldrinPlus */ import _ from 'underscore'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import axios from './lib/utils/axios_utils'; import { visitUrl } from './lib/utils/url_utility'; import { isObject } from './lib/utils/type_utility'; @@ -212,25 +213,17 @@ GitLabDropdownRemote = (function() { }; GitLabDropdownRemote.prototype.fetchData = function() { - return $.ajax({ - url: this.dataEndpoint, - dataType: this.options.dataType, - beforeSend: (function(_this) { - return function() { - if (_this.options.beforeSend) { - return _this.options.beforeSend(); - } - }; - })(this), - success: (function(_this) { - return function(data) { - if (_this.options.success) { - return _this.options.success(data); - } - }; - })(this) - }); - // Fetch the data through ajax if the data is a string + if (this.options.beforeSend) { + this.options.beforeSend(); + } + + // Fetch the data through ajax if the data is a string + return axios.get(this.dataEndpoint) + .then(({ data }) => { + if (this.options.success) { + return this.options.success(data); + } + }); }; return GitLabDropdownRemote; -- cgit v1.2.1 From a491bcc2b161b7c14e48e909be249170136a4d39 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 29 Jan 2018 12:10:07 +0000 Subject: Converted graphs_show to axios --- app/assets/javascripts/graphs/graphs_show.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/graphs/graphs_show.js b/app/assets/javascripts/graphs/graphs_show.js index 36bad6db3e1..b670e907a5c 100644 --- a/app/assets/javascripts/graphs/graphs_show.js +++ b/app/assets/javascripts/graphs/graphs_show.js @@ -1,11 +1,13 @@ +import flash from '../flash'; +import { __ } from '../locale'; +import axios from '../lib/utils/axios_utils'; import ContributorsStatGraph from './stat_graph_contributors'; document.addEventListener('DOMContentLoaded', () => { - $.ajax({ - type: 'GET', - url: document.querySelector('.js-graphs-show').dataset.projectGraphPath, - dataType: 'json', - success(data) { + const url = document.querySelector('.js-graphs-show').dataset.projectGraphPath; + + axios.get(url) + .then(({ data }) => { const graph = new ContributorsStatGraph(); graph.init(data); @@ -16,6 +18,6 @@ document.addEventListener('DOMContentLoaded', () => { $('.stat-graph').fadeIn(); $('.loading-graph').hide(); - }, - }); + }) + .catch(() => flash(__('Error fetching contributors data.'))); }); -- cgit v1.2.1 From dab430f0316e997657d75b1478394bfc71c6a2bd Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 29 Jan 2018 12:15:13 +0000 Subject: Converted group_label_subscription to axios --- app/assets/javascripts/group_label_subscription.js | 27 +++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js index befaebb635e..df9429b1e02 100644 --- a/app/assets/javascripts/group_label_subscription.js +++ b/app/assets/javascripts/group_label_subscription.js @@ -1,3 +1,7 @@ +import axios from './lib/utils/axios_utils'; +import flash from './flash'; +import { __ } from './locale'; + export default class GroupLabelSubscription { constructor(container) { const $container = $(container); @@ -13,14 +17,12 @@ export default class GroupLabelSubscription { event.preventDefault(); const url = this.$unsubscribeButtons.attr('data-url'); - - $.ajax({ - type: 'POST', - url, - }).done(() => { - this.toggleSubscriptionButtons(); - this.$unsubscribeButtons.removeAttr('data-url'); - }); + axios.post(url) + .then(() => { + this.toggleSubscriptionButtons(); + this.$unsubscribeButtons.removeAttr('data-url'); + }) + .catch(() => flash(__('There was an error when unsubscribing from this label.'))); } subscribe(event) { @@ -31,12 +33,9 @@ export default class GroupLabelSubscription { this.$unsubscribeButtons.attr('data-url', url); - $.ajax({ - type: 'POST', - url, - }).done(() => { - this.toggleSubscriptionButtons(); - }); + axios.post(url) + .then(() => this.toggleSubscriptionButtons()) + .catch(() => flash(__('There was an error when subscribing to this label.'))); } toggleSubscriptionButtons() { -- cgit v1.2.1 From ba96309fb895bf590adec66f6445b3f34da3310b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 29 Jan 2018 12:23:21 +0000 Subject: Converted issuable_index to axios --- app/assets/javascripts/issuable_index.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index c3e0acdff66..aafadcb7d4e 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -1,3 +1,6 @@ +import axios from './lib/utils/axios_utils'; +import flash from './flash'; +import { __ } from './locale'; import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; @@ -23,20 +26,19 @@ export default class IssuableIndex { $('.incoming-email-token-reset').on('click', (e) => { e.preventDefault(); - $.ajax({ - type: 'PUT', - url: $('.incoming-email-token-reset').attr('href'), - dataType: 'json', - success(response) { - $('#issuable_email').val(response.new_address).focus(); - }, - beforeSend() { - $('.incoming-email-token-reset').text('resetting...'); - }, - complete() { + $('.incoming-email-token-reset').text('resetting...'); + + axios.put($('.incoming-email-token-reset').attr('href')) + .then(({ data }) => { + $('#issuable_email').val(data.new_address).focus(); + $('.incoming-email-token-reset').text('reset it'); - }, - }); + }) + .catch(() => { + flash(__('There was an error when reseting email token.')); + + $('.incoming-email-token-reset').text('reset it'); + }); }); } } -- cgit v1.2.1 From 5792faf4ae45b3380478bf9abaf9993c711354b3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 29 Jan 2018 12:32:04 +0000 Subject: Converted issuable_bulk_update_actions to axios --- app/assets/javascripts/issuable_bulk_update_actions.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index b124fafec70..8c1b2e78ca4 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -1,5 +1,6 @@ /* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */ import _ from 'underscore'; +import axios from './lib/utils/axios_utils'; import Flash from './flash'; export default { @@ -22,15 +23,9 @@ export default { }, submit() { - const _this = this; - const xhr = $.ajax({ - url: this.form.attr('action'), - method: this.form.attr('method'), - dataType: 'JSON', - data: this.getFormDataAsObject() - }); - xhr.done(() => window.location.reload()); - xhr.fail(() => this.onFormSubmitFailure()); + axios[this.form.attr('method')](this.form.attr('action'), this.getFormDataAsObject()) + .then(() => window.location.reload()) + .catch(() => this.onFormSubmitFailure()); }, onFormSubmitFailure() { -- cgit v1.2.1 From 0c2c8773fbcad9b837500b12f01de754e4bd36a6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 29 Jan 2018 13:00:15 +0000 Subject: Converted integration_settings_form to axios --- .../integrations/integration_settings_form.js | 48 ++++---- .../integrations/integration_settings_form_spec.js | 128 +++++++++++++-------- 2 files changed, 101 insertions(+), 75 deletions(-) diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index 32415a8791f..3f27cfc2f6d 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -1,4 +1,5 @@ -import Flash from '../flash'; +import axios from '../lib/utils/axios_utils'; +import flash from '../flash'; export default class IntegrationSettingsForm { constructor(formSelector) { @@ -95,29 +96,26 @@ export default class IntegrationSettingsForm { */ testSettings(formData) { this.toggleSubmitBtnState(true); - $.ajax({ - type: 'PUT', - url: this.testEndPoint, - data: formData, - }) - .done((res) => { - if (res.error) { - new Flash(`${res.message} ${res.service_response}`, 'alert', document, { - title: 'Save anyway', - clickHandler: (e) => { - e.preventDefault(); - this.$form.submit(); - }, - }); - } else { - this.$form.submit(); - } - }) - .fail(() => { - new Flash('Something went wrong on our end.'); - }) - .always(() => { - this.toggleSubmitBtnState(false); - }); + + return axios.put(this.testEndPoint, formData) + .then(({ data }) => { + if (data.error) { + flash(`${data.message} ${data.service_response}`, 'alert', document, { + title: 'Save anyway', + clickHandler: (e) => { + e.preventDefault(); + this.$form.submit(); + }, + }); + } else { + this.$form.submit(); + } + + this.toggleSubmitBtnState(false); + }) + .catch(() => { + flash('Something went wrong on our end.'); + this.toggleSubmitBtnState(false); + }); } } diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 9033eb9ce02..d0fba908e34 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -1,3 +1,5 @@ +import MockAdaptor from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import IntegrationSettingsForm from '~/integrations/integration_settings_form'; describe('IntegrationSettingsForm', () => { @@ -109,91 +111,117 @@ describe('IntegrationSettingsForm', () => { describe('testSettings', () => { let integrationSettingsForm; let formData; + let mock; beforeEach(() => { + mock = new MockAdaptor(axios); + + spyOn(axios, 'put').and.callThrough(); + integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); formData = integrationSettingsForm.$form.serialize(); }); - it('should make an ajax request with provided `formData`', () => { - const deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred.promise()); + afterEach(() => { + mock.restore(); + }); - integrationSettingsForm.testSettings(formData); + it('should make an ajax request with provided `formData`', (done) => { + integrationSettingsForm.testSettings(formData) + .then(() => { + expect(axios.put).toHaveBeenCalledWith(integrationSettingsForm.testEndPoint, formData); - expect($.ajax).toHaveBeenCalledWith({ - type: 'PUT', - url: integrationSettingsForm.testEndPoint, - data: formData, - }); + done(); + }) + .catch(done.fail); }); - it('should show error Flash with `Save anyway` action if ajax request responds with error in test', () => { + it('should show error Flash with `Save anyway` action if ajax request responds with error in test', (done) => { const errorMessage = 'Test failed.'; - const deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred.promise()); - - integrationSettingsForm.testSettings(formData); + mock.onPut(integrationSettingsForm.testEndPoint).reply(200, { + error: true, + message: errorMessage, + service_response: 'some error', + }); - deferred.resolve({ error: true, message: errorMessage, service_response: 'some error' }); + integrationSettingsForm.testSettings(formData) + .then(() => { + const $flashContainer = $('.flash-container'); + expect($flashContainer.find('.flash-text').text().trim()).toEqual('Test failed. some error'); + expect($flashContainer.find('.flash-action')).toBeDefined(); + expect($flashContainer.find('.flash-action').text().trim()).toEqual('Save anyway'); - const $flashContainer = $('.flash-container'); - expect($flashContainer.find('.flash-text').text().trim()).toEqual('Test failed. some error'); - expect($flashContainer.find('.flash-action')).toBeDefined(); - expect($flashContainer.find('.flash-action').text().trim()).toEqual('Save anyway'); + done(); + }) + .catch(done.fail); }); - it('should submit form if ajax request responds without any error in test', () => { - const deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred.promise()); + it('should submit form if ajax request responds without any error in test', (done) => { + spyOn(integrationSettingsForm.$form, 'submit'); - integrationSettingsForm.testSettings(formData); + mock.onPut(integrationSettingsForm.testEndPoint).reply(200, { + error: false, + }); - spyOn(integrationSettingsForm.$form, 'submit'); - deferred.resolve({ error: false }); + integrationSettingsForm.testSettings(formData) + .then(() => { + expect(integrationSettingsForm.$form.submit).toHaveBeenCalled(); - expect(integrationSettingsForm.$form.submit).toHaveBeenCalled(); + done(); + }) + .catch(done.fail); }); - it('should submit form when clicked on `Save anyway` action of error Flash', () => { - const errorMessage = 'Test failed.'; - const deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred.promise()); + it('should submit form when clicked on `Save anyway` action of error Flash', (done) => { + spyOn(integrationSettingsForm.$form, 'submit'); - integrationSettingsForm.testSettings(formData); + const errorMessage = 'Test failed.'; + mock.onPut(integrationSettingsForm.testEndPoint).reply(200, { + error: true, + message: errorMessage, + }); - deferred.resolve({ error: true, message: errorMessage }); + integrationSettingsForm.testSettings(formData) + .then(() => { + const $flashAction = $('.flash-container .flash-action'); + expect($flashAction).toBeDefined(); - const $flashAction = $('.flash-container .flash-action'); - expect($flashAction).toBeDefined(); + $flashAction.get(0).click(); + }) + .then(() => { + expect(integrationSettingsForm.$form.submit).toHaveBeenCalled(); - spyOn(integrationSettingsForm.$form, 'submit'); - $flashAction.get(0).click(); - expect(integrationSettingsForm.$form.submit).toHaveBeenCalled(); + done(); + }) + .catch(done.fail); }); - it('should show error Flash if ajax request failed', () => { + it('should show error Flash if ajax request failed', (done) => { const errorMessage = 'Something went wrong on our end.'; - const deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred.promise()); - integrationSettingsForm.testSettings(formData); + mock.onPut(integrationSettingsForm.testEndPoint).networkError(); - deferred.reject(); + integrationSettingsForm.testSettings(formData) + .then(() => { + expect($('.flash-container .flash-text').text().trim()).toEqual(errorMessage); - expect($('.flash-container .flash-text').text().trim()).toEqual(errorMessage); + done(); + }) + .catch(done.fail); }); - it('should always call `toggleSubmitBtnState` with `false` once request is completed', () => { - const deferred = $.Deferred(); - spyOn($, 'ajax').and.returnValue(deferred.promise()); - - integrationSettingsForm.testSettings(formData); + it('should always call `toggleSubmitBtnState` with `false` once request is completed', (done) => { + mock.onPut(integrationSettingsForm.testEndPoint).networkError(); spyOn(integrationSettingsForm, 'toggleSubmitBtnState'); - deferred.reject(); - expect(integrationSettingsForm.toggleSubmitBtnState).toHaveBeenCalledWith(false); + integrationSettingsForm.testSettings(formData) + .then(() => { + expect(integrationSettingsForm.toggleSubmitBtnState).toHaveBeenCalledWith(false); + + done(); + }) + .catch(done.fail); }); }); }); -- cgit v1.2.1 From 2b66879ea2ae8ac3ae7015eb2b083770f973ad4e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 29 Jan 2018 14:29:08 +0000 Subject: fixed issuable_spec.js --- spec/javascripts/issuable_spec.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js index 5a9112716f4..d53ffecbd35 100644 --- a/spec/javascripts/issuable_spec.js +++ b/spec/javascripts/issuable_spec.js @@ -1,3 +1,5 @@ +import MockAdaptor from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import IssuableIndex from '~/issuable_index'; describe('Issuable', () => { @@ -19,6 +21,8 @@ describe('Issuable', () => { }); describe('resetIncomingEmailToken', () => { + let mock; + beforeEach(() => { const element = document.createElement('a'); element.classList.add('incoming-email-token-reset'); @@ -30,14 +34,28 @@ describe('Issuable', () => { document.body.appendChild(input); Issuable = new IssuableIndex('issue_'); + + mock = new MockAdaptor(axios); + + mock.onPut('foo').reply(200, { + new_address: 'testing123', + }); }); - it('should send request to reset email token', () => { - spyOn(jQuery, 'ajax').and.callThrough(); + afterEach(() => { + mock.restore(); + }); + + it('should send request to reset email token', (done) => { + spyOn(axios, 'put').and.callThrough(); document.querySelector('.incoming-email-token-reset').click(); - expect(jQuery.ajax).toHaveBeenCalled(); - expect(jQuery.ajax.calls.argsFor(0)[0].url).toEqual('foo'); + setTimeout(() => { + expect(axios.put).toHaveBeenCalledWith('foo'); + expect($('#issuable_email').val()).toBe('testing123'); + + done(); + }); }); }); }); -- cgit v1.2.1 From df44c59050b10d9312081782c1620aaf9969dcf8 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 29 Jan 2018 15:56:19 +0000 Subject: Close and do not reload MR diffs when source branch is deleted --- app/services/merge_requests/refresh_service.rb | 16 ++++++++++++-- ...w-fix-lost-diffs-when-source-branch-deleted.yml | 5 +++++ spec/models/merge_request_spec.rb | 2 +- .../merge_requests/refresh_service_spec.rb | 25 ++++++++++++++++++++-- 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/osw-fix-lost-diffs-when-source-branch-deleted.yml diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 9f05535d4d4..262622f8bd0 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -9,7 +9,8 @@ module MergeRequests Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits)) # Be sure to close outstanding MRs before reloading them to avoid generating an # empty diff during a manual merge - close_merge_requests + close_upon_missing_source_branch_ref + post_merge_manually_merged reload_merge_requests reset_merge_when_pipeline_succeeds mark_pending_todos_done @@ -29,11 +30,22 @@ module MergeRequests private + def close_upon_missing_source_branch_ref + # MergeRequest#reload_diff ignores not opened MRs. This means it won't + # create an `empty` diff for `closed` MRs without a source branch, keeping + # the latest diff state as the last _valid_ one. + merge_requests_for_source_branch.reject(&:source_branch_exists?).each do |mr| + MergeRequests::CloseService + .new(mr.target_project, @current_user) + .execute(mr) + end + end + # Collect open merge requests that target same branch we push into # and close if push to master include last commit from merge request # We need this to close(as merged) merge requests that were merged into # target branch manually - def close_merge_requests + def post_merge_manually_merged commit_ids = @commits.map(&:id) merge_requests = @project.merge_requests.preload(:latest_merge_request_diff).opened.where(target_branch: @branch_name).to_a merge_requests = merge_requests.select(&:diff_head_commit) diff --git a/changelogs/unreleased/osw-fix-lost-diffs-when-source-branch-deleted.yml b/changelogs/unreleased/osw-fix-lost-diffs-when-source-branch-deleted.yml new file mode 100644 index 00000000000..1cffb213f23 --- /dev/null +++ b/changelogs/unreleased/osw-fix-lost-diffs-when-source-branch-deleted.yml @@ -0,0 +1,5 @@ +--- +title: Close and do not reload MR diffs when source branch is deleted +merge_request: +author: +type: fixed diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 15f9c68e400..eb9690df313 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1539,7 +1539,7 @@ describe MergeRequest do expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1) end - it "executs diff cache service" do + it "executes diff cache service" do expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject) subject.reload_diff diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7a01d3dd698..7c3374c6113 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -55,11 +55,12 @@ describe MergeRequests::RefreshService do before do allow(refresh_service).to receive(:execute_hooks) - refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') - reload_mrs end it 'executes hooks with update action' do + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + expect(refresh_service).to have_received(:execute_hooks) .with(@merge_request, 'update', old_rev: @oldrev) @@ -72,6 +73,26 @@ describe MergeRequests::RefreshService do expect(@build_failed_todo).to be_done expect(@fork_build_failed_todo).to be_done end + + context 'when source branch ref does not exists' do + before do + DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch) + end + + it 'closes MRs without source branch ref' do + expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') } + .to change { @merge_request.reload.state } + .from('opened') + .to('closed') + + expect(@fork_merge_request.reload).to be_open + end + + it 'does not change the merge request diff' do + expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') } + .not_to change { @merge_request.reload.merge_request_diff } + end + end end context 'when pipeline exists for the source branch' do -- cgit v1.2.1 From 7b276a10764f629393ddac1fbd237d2efb69dfd6 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 29 Jan 2018 16:18:31 +0000 Subject: Fix an order dependency in a spec --- spec/models/commit_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index f8a98b43e46..959383ff0b7 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -228,7 +228,7 @@ eos it { expect(data).to be_a(Hash) } it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') } it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46Z') } - it { expect(data[:added]).to eq(["bar/branch-test.txt"]) } + it { expect(data[:added]).to contain_exactly("bar/branch-test.txt") } it { expect(data[:modified]).to eq([]) } it { expect(data[:removed]).to eq([]) } end @@ -532,8 +532,8 @@ eos let(:commit2) { merge_request1.merge_request_diff.commits.first } it 'returns merge_requests that introduced that commit' do - expect(commit1.merge_requests).to eq([merge_request1, merge_request2]) - expect(commit2.merge_requests).to eq([merge_request1]) + expect(commit1.merge_requests).to contain_exactly(merge_request1, merge_request2) + expect(commit2.merge_requests).to contain_exactly(merge_request1) end end end -- cgit v1.2.1 From f7811b4b0b22b863a25cb285a5972b0925f60969 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 29 Jan 2018 17:53:48 +0000 Subject: Replace $.post in edit blob with axios --- app/assets/javascripts/blob_edit/edit_blob.js | 15 ++++++++++----- spec/features/projects/blobs/edit_spec.rb | 22 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index b37988a674d..a25f7fb3dcd 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -1,5 +1,8 @@ /* global ace */ +import axios from '~/lib/utils/axios_utils'; +import createFlash from '~/flash'; +import { __ } from '~/locale'; import TemplateSelectorMediator from '../blob/file_template_mediator'; export default class EditBlob { @@ -56,12 +59,14 @@ export default class EditBlob { if (paneId === '#preview') { this.$toggleButton.hide(); - return $.post(currentLink.data('preview-url'), { + axios.post(currentLink.data('preview-url'), { content: this.editor.getValue(), - }, (response) => { - currentPane.empty().append(response); - return currentPane.renderGFM(); - }); + }) + .then(({ data }) => { + currentPane.empty().append(data); + currentPane.renderGFM(); + }) + .catch(() => createFlash(__('An error occurred previewing the blob'))); } this.$toggleButton.show(); diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 69e4c9f04a1..89d3bd24b89 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -17,12 +17,15 @@ feature 'Editing file blob', :js do sign_in(user) end - def edit_and_commit + def edit_and_commit(commit_changes: true) wait_for_requests find('.js-edit-blob').click find('#editor') execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")') - click_button 'Commit changes' + + if commit_changes + click_button 'Commit changes' + end end context 'from MR diff' do @@ -39,13 +42,26 @@ feature 'Editing file blob', :js do context 'from blob file path' do before do visit project_blob_path(project, tree_join(branch, file_path)) - edit_and_commit end it 'updates content' do + edit_and_commit + expect(page).to have_content 'successfully committed' expect(page).to have_content 'NextFeature' end + + it 'previews content' do + edit_and_commit(commit_changes: false) + click_link 'Preview changes' + wait_for_requests + + old_line_count = page.all('.line_holder.old').size + new_line_count = page.all('.line_holder.new').size + + expect(old_line_count).to be > 0 + expect(new_line_count).to be > 0 + end end end -- cgit v1.2.1 From 395e500f0143258c956f11e09dda676d7ed2b554 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 29 Jan 2018 16:29:25 -0200 Subject: Remove tap and use simplified method call --- lib/api/entities.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ac5b6e518fd..cb222697f32 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -513,7 +513,8 @@ module API # See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more # information. expose :merge_status do |merge_request| - merge_request.tap(&:check_if_can_be_merged).merge_status + merge_request.check_if_can_be_merged + merge_request.merge_status end expose :diff_head_sha, as: :sha expose :merge_commit_sha -- cgit v1.2.1 From e2065ed6c71e544253fbf3effaea8434836e7d12 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 29 Jan 2018 13:49:04 -0600 Subject: add noData empty state to prometheus graphs --- app/assets/javascripts/monitoring/components/dashboard.vue | 8 +++++++- app/assets/javascripts/monitoring/components/empty_state.vue | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 025e38ea99a..5afae93724b 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -76,7 +76,13 @@ .then(data => this.store.storeDeploymentData(data)) .catch(() => new Flash('Error getting deployment information.')), ]) - .then(() => { this.showEmptyState = false; }) + .then(() => { + if (this.store.groups.length < 1) { + this.state = 'noData'; + return; + } + this.showEmptyState = false; + }) .catch(() => { this.state = 'unableToConnect'; }); }, diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue index 87d1975d5ad..56cd60c583b 100644 --- a/app/assets/javascripts/monitoring/components/empty_state.vue +++ b/app/assets/javascripts/monitoring/components/empty_state.vue @@ -34,16 +34,23 @@ svgUrl: this.emptyGettingStartedSvgPath, title: 'Get started with performance monitoring', description: `Stay updated about the performance and health -of your environment by configuring Prometheus to monitor your deployments.`, + of your environment by configuring Prometheus to monitor your deployments.`, buttonText: 'Configure Prometheus', }, loading: { svgUrl: this.emptyLoadingSvgPath, title: 'Waiting for performance data', description: `Creating graphs uses the data from the Prometheus server. -If this takes a long time, ensure that data is available.`, + If this takes a long time, ensure that data is available.`, buttonText: 'View documentation', }, + noData: { + svgUrl: this.emptyUnableToConnectSvgPath, + title: 'No data found', + description: `You are connected to the Prometheus server, but there is currently + no data to display.`, + buttonText: 'Configure Prometheus', + }, unableToConnect: { svgUrl: this.emptyUnableToConnectSvgPath, title: 'Unable to connect to Prometheus server', -- cgit v1.2.1 From 233a9861113df678e1f54dfbd13d9ab23fd5d5a0 Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Mon, 29 Jan 2018 14:56:43 -0600 Subject: Fix grape-route-helper route shadowing Bringing in https://github.com/reprah/grape-route-helpers/pull/21 as a monkey patch since the grape-route-helpers project seems to be abandoned --- config/initializers/grape_route_helpers_fix.rb | 16 ++++++++++++++++ spec/initializers/grape_route_helpers_fix_spec.rb | 14 ++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 spec/initializers/grape_route_helpers_fix_spec.rb diff --git a/config/initializers/grape_route_helpers_fix.rb b/config/initializers/grape_route_helpers_fix.rb index d3cf9e453d0..612cca3dfbd 100644 --- a/config/initializers/grape_route_helpers_fix.rb +++ b/config/initializers/grape_route_helpers_fix.rb @@ -1,5 +1,21 @@ if defined?(GrapeRouteHelpers) module GrapeRouteHelpers + module AllRoutes + # Bringing in PR https://github.com/reprah/grape-route-helpers/pull/21 due to abandonment. + # + # Without the following fix, when two helper methods are the same, but have different arguments + # (for example: api_v1_cats_owners_path(id: 1) vs api_v1_cats_owners_path(id: 1, owner_id: 2)) + # if the helper method with the least number of arguments is defined first (because the route was defined first) + # then it will shadow the longer route. + # + # The fix is to sort descending by amount of arguments + def decorated_routes + @decorated_routes ||= all_routes + .map { |r| DecoratedRoute.new(r) } + .sort_by { |r| -r.dynamic_path_segments.count } + end + end + class DecoratedRoute # GrapeRouteHelpers gem tries to parse the versions # from a string, not supporting Grape `version` array definition. diff --git a/spec/initializers/grape_route_helpers_fix_spec.rb b/spec/initializers/grape_route_helpers_fix_spec.rb new file mode 100644 index 00000000000..2cf5924128f --- /dev/null +++ b/spec/initializers/grape_route_helpers_fix_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' +require_relative '../../config/initializers/grape_route_helpers_fix' + +describe 'route shadowing' do + include GrapeRouteHelpers::NamedRouteMatcher + + it 'does not occur' do + path = api_v4_projects_merge_requests_path(id: 1) + expect(path).to eq('/api/v4/projects/1/merge_requests') + + path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3) + expect(path).to eq('/api/v4/projects/1/merge_requests/3') + end +end -- cgit v1.2.1 From 8f965d0fa39b5800b0bb135791a6817a41007d56 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 26 Jan 2018 16:06:01 -0800 Subject: Fix JIRA not working when a trailing slash is included Leaving a trailing slash in the context option causes the jira-ruby gem to add an extra slash in HTTP requests to the URL, preventing JIRA from working properly. Closes #42494 --- app/models/project_services/jira_service.rb | 2 +- .../unreleased/sh-fix-jira-trailing-slash.yml | 5 +++++ spec/models/project_services/jira_service_spec.rb | 23 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/sh-fix-jira-trailing-slash.yml diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 2be35b6ea9d..23147d7f666 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -43,7 +43,7 @@ class JiraService < IssueTrackerService username: self.username, password: self.password, site: URI.join(url, '/').to_s, - context_path: url.path, + context_path: url.path.chomp('/'), auth_type: :basic, read_timeout: 120, use_cookies: true, diff --git a/changelogs/unreleased/sh-fix-jira-trailing-slash.yml b/changelogs/unreleased/sh-fix-jira-trailing-slash.yml new file mode 100644 index 00000000000..786f6cd3727 --- /dev/null +++ b/changelogs/unreleased/sh-fix-jira-trailing-slash.yml @@ -0,0 +1,5 @@ +--- +title: Fix JIRA not working when a trailing slash is included +merge_request: +author: +type: fixed diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index c9b3c6cf602..1eaaadf56c5 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -3,6 +3,29 @@ require 'spec_helper' describe JiraService do include Gitlab::Routing + describe '#options' do + let(:service) do + described_class.new( + project: build_stubbed(:project), + active: true, + username: 'username', + password: 'test', + jira_issue_transition_id: 24, + url: 'http://jira.test.com/path/' + ) + end + + it 'sets the URL properly' do + # jira-ruby gem parses the URI and handles trailing slashes + # fine: https://github.com/sumoheavy/jira-ruby/blob/v1.4.1/lib/jira/http_client.rb#L59 + expect(service.options[:site]).to eq('http://jira.test.com/') + end + + it 'leaves out trailing slashes in context' do + expect(service.options[:context_path]).to eq('/path') + end + end + describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } -- cgit v1.2.1 From dfe9d49534a26e86c773d81a4f5b81165efdd506 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Tue, 30 Jan 2018 10:00:33 +0000 Subject: Update CHANGELOG.md for 10.4.2 [ci skip] --- CHANGELOG.md | 16 ++++++++++++++++ changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml | 5 ----- ...ess-not-visible-when-project-visibility-is-public.yml | 5 ----- ...edconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml | 5 ----- ...lready-exists-and-is-not-an-empty-directory-error.yml | 6 ------ changelogs/unreleased/fix-cache-clear-windows.yml | 5 ----- changelogs/unreleased/fix-postgresql-table-grant.yml | 5 ----- 7 files changed, 16 insertions(+), 31 deletions(-) delete mode 100644 changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml delete mode 100644 changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml delete mode 100644 changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml delete mode 100644 changelogs/unreleased/42327-import-from-gitlab-com-fails-destination-already-exists-and-is-not-an-empty-directory-error.yml delete mode 100644 changelogs/unreleased/fix-cache-clear-windows.yml delete mode 100644 changelogs/unreleased/fix-postgresql-table-grant.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 248c85304a9..5fc97c06f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.4.2 (2018-01-30) + +### Fixed (6 changes) + +- Fix copy/paste on iOS devices due to a bug in webkit. !15804 +- Fix missing "allow users to request access" option in public project permissions. !16485 +- Fix encoding issue when counting commit count. !16637 +- Fixes destination already exists, and some particular service errors on Import/Export error. !16714 +- Fix cache clear bug withg using : on Windows. !16740 +- Use has_table_privilege for TRIGGER on PostgreSQL. + +### Changed (1 change) + +- Vendor Auto DevOps template with DAST security checks enabled. !16691 + + ## 10.4.1 (2018-01-24) ### Fixed (4 changes) diff --git a/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml b/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml deleted file mode 100644 index f4c44983736..00000000000 --- a/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix copy/paste on iOS devices due to a bug in webkit -merge_request: 15804 -author: -type: fixed diff --git a/changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml b/changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml deleted file mode 100644 index 38684cd3c44..00000000000 --- a/changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix missing "allow users to request access" option in public project permissions -merge_request: 16485 -author: -type: fixed 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 deleted file mode 100644 index c64bee9126e..00000000000 --- a/changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix encoding issue when counting commit count -merge_request: 16637 -author: -type: fixed diff --git a/changelogs/unreleased/42327-import-from-gitlab-com-fails-destination-already-exists-and-is-not-an-empty-directory-error.yml b/changelogs/unreleased/42327-import-from-gitlab-com-fails-destination-already-exists-and-is-not-an-empty-directory-error.yml deleted file mode 100644 index 660f4f5d42c..00000000000 --- a/changelogs/unreleased/42327-import-from-gitlab-com-fails-destination-already-exists-and-is-not-an-empty-directory-error.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fixes destination already exists, and some particular service errors on Import/Export - error -merge_request: 16714 -author: -type: fixed diff --git a/changelogs/unreleased/fix-cache-clear-windows.yml b/changelogs/unreleased/fix-cache-clear-windows.yml deleted file mode 100644 index 2be6bac004b..00000000000 --- a/changelogs/unreleased/fix-cache-clear-windows.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Fix cache clear bug withg using : on Windows' -merge_request: 16740 -author: -type: fixed diff --git a/changelogs/unreleased/fix-postgresql-table-grant.yml b/changelogs/unreleased/fix-postgresql-table-grant.yml deleted file mode 100644 index 1c6559f6f73..00000000000 --- a/changelogs/unreleased/fix-postgresql-table-grant.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use has_table_privilege for TRIGGER on PostgreSQL -merge_request: -author: -type: fixed -- cgit v1.2.1 From 33ed96229b09c062ec9221b192cf7dd0c214b7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 30 Jan 2018 11:08:22 +0100 Subject: Don't run scripts/lint-changelog-yaml in scripts/static-analysis but only in the 'docs lint' job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- scripts/static-analysis | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/static-analysis b/scripts/static-analysis index b3895292ab4..bdb88f3cb57 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -35,7 +35,6 @@ tasks = [ %w[bundle exec rubocop --parallel], %w[bundle exec rake gettext:lint], %w[bundle exec rake lint:static_verification], - %w[scripts/lint-changelog-yaml], %w[scripts/lint-conflicts.sh], %w[scripts/lint-rugged] ] -- cgit v1.2.1 From 5a22b588d02a08c949ee772fa9008bd1a4e38829 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 25 Jan 2018 00:55:49 -0600 Subject: Add createNewItemFromValue option and clearDropdown method Part of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4110 --- app/assets/javascripts/create_item_dropdown.js | 44 +++++--- spec/javascripts/create_item_dropdown_spec.js | 139 +++++++++++++++++++------ 2 files changed, 139 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/create_item_dropdown.js b/app/assets/javascripts/create_item_dropdown.js index 488db023ee7..42e9e568170 100644 --- a/app/assets/javascripts/create_item_dropdown.js +++ b/app/assets/javascripts/create_item_dropdown.js @@ -12,6 +12,7 @@ export default class CreateItemDropdown { this.fieldName = options.fieldName; this.onSelect = options.onSelect || (() => {}); this.getDataOption = options.getData; + this.createNewItemFromValueOption = options.createNewItemFromValue; this.$dropdown = options.$dropdown; this.$dropdownContainer = this.$dropdown.parent(); this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer'); @@ -30,15 +31,15 @@ export default class CreateItemDropdown { filterable: true, remote: false, search: { - fields: ['title'], + fields: ['text'], }, selectable: true, toggleLabel(selected) { - return (selected && 'id' in selected) ? selected.title : this.defaultToggleLabel; + return (selected && 'id' in selected) ? _.escape(selected.title) : this.defaultToggleLabel; }, fieldName: this.fieldName, text(item) { - return _.escape(item.title); + return _.escape(item.text); }, id(item) { return _.escape(item.id); @@ -51,6 +52,11 @@ export default class CreateItemDropdown { }); } + clearDropdown() { + this.$dropdownContainer.find('.dropdown-content').html(''); + this.$dropdownContainer.find('.dropdown-input-field').val(''); + } + bindEvents() { this.$createButton.on('click', this.onClickCreateWildcard.bind(this)); } @@ -58,9 +64,13 @@ export default class CreateItemDropdown { onClickCreateWildcard(e) { e.preventDefault(); + this.refreshData(); + this.$dropdown.data('glDropdown').selectRowAtIndex(); + } + + refreshData() { // Refresh the dropdown's data, which ends up calling `getData` this.$dropdown.data('glDropdown').remote.execute(); - this.$dropdown.data('glDropdown').selectRowAtIndex(); } getData(term, callback) { @@ -79,20 +89,28 @@ export default class CreateItemDropdown { }); } - toggleCreateNewButton(item) { - if (item) { - this.selectedItem = { - title: item, - id: item, - text: item, - }; + createNewItemFromValue(newValue) { + if (this.createNewItemFromValueOption) { + return this.createNewItemFromValueOption(newValue); + } + + return { + title: newValue, + id: newValue, + text: newValue, + }; + } + + toggleCreateNewButton(newValue) { + if (newValue) { + this.selectedItem = this.createNewItemFromValue(newValue); this.$dropdownContainer .find('.js-dropdown-create-new-item code') - .text(item); + .text(newValue); } - this.toggleFooter(!item); + this.toggleFooter(!newValue); } toggleFooter(toggleState) { diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js index c8b00a4f553..143137c23ec 100644 --- a/spec/javascripts/create_item_dropdown_spec.js +++ b/spec/javascripts/create_item_dropdown_spec.js @@ -18,54 +18,67 @@ describe('CreateItemDropdown', () => { preloadFixtures('static/create_item_dropdown.html.raw'); let $wrapperEl; + let createItemDropdown; + + function createItemAndClearInput(text) { + // Filter for the new item + $wrapperEl.find('.dropdown-input-field') + .val(text) + .trigger('input'); + + // Create the new item + const $createButton = $wrapperEl.find('.js-dropdown-create-new-item'); + $createButton.click(); + + // Clear out the filter + $wrapperEl.find('.dropdown-input-field') + .val('') + .trigger('input'); + } beforeEach(() => { loadFixtures('static/create_item_dropdown.html.raw'); $wrapperEl = $('.js-create-item-dropdown-fixture-root'); - - // eslint-disable-next-line no-new - new CreateItemDropdown({ - $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'), - defaultToggleLabel: 'All variables', - fieldName: 'variable[environment]', - getData: (term, callback) => { - callback(DROPDOWN_ITEM_DATA); - }, - }); }); afterEach(() => { $wrapperEl.remove(); }); - it('should have a dropdown item for each piece of data', () => { - // Get the data in the dropdown - $('.js-dropdown-menu-toggle').click(); + describe('items', () => { + beforeEach(() => { + createItemDropdown = new CreateItemDropdown({ + $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'), + defaultToggleLabel: 'All variables', + fieldName: 'variable[environment]', + getData: (term, callback) => { + callback(DROPDOWN_ITEM_DATA); + }, + }); + }); + + it('should have a dropdown item for each piece of data', () => { + // Get the data in the dropdown + $('.js-dropdown-menu-toggle').click(); - const $itemEls = $wrapperEl.find('.js-dropdown-content a'); - expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length); + const $itemEls = $wrapperEl.find('.js-dropdown-content a'); + expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length); + }); }); describe('created items', () => { const NEW_ITEM_TEXT = 'foobarbaz'; - function createItemAndClearInput(text) { - // Filter for the new item - $wrapperEl.find('.dropdown-input-field') - .val(text) - .trigger('input'); - - // Create the new item - const $createButton = $wrapperEl.find('.js-dropdown-create-new-item'); - $createButton.click(); - - // Clear out the filter - $wrapperEl.find('.dropdown-input-field') - .val('') - .trigger('input'); - } - beforeEach(() => { + createItemDropdown = new CreateItemDropdown({ + $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'), + defaultToggleLabel: 'All variables', + fieldName: 'variable[environment]', + getData: (term, callback) => { + callback(DROPDOWN_ITEM_DATA); + }, + }); + // Open the dropdown $('.js-dropdown-menu-toggle').click(); @@ -103,4 +116,68 @@ describe('CreateItemDropdown', () => { expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length); }); }); + + describe('clearDropdown()', () => { + beforeEach(() => { + createItemDropdown = new CreateItemDropdown({ + $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'), + defaultToggleLabel: 'All variables', + fieldName: 'variable[environment]', + getData: (term, callback) => { + callback(DROPDOWN_ITEM_DATA); + }, + }); + }); + + it('should clear all data and filter input', () => { + const filterInput = $wrapperEl.find('.dropdown-input-field'); + + // Get the data in the dropdown + $('.js-dropdown-menu-toggle').click(); + + // Filter for an item + filterInput + .val('one') + .trigger('input'); + + const $itemElsAfterFilter = $wrapperEl.find('.js-dropdown-content a'); + expect($itemElsAfterFilter.length).toEqual(1); + + createItemDropdown.clearDropdown(); + + const $itemElsAfterClear = $wrapperEl.find('.js-dropdown-content a'); + expect($itemElsAfterClear.length).toEqual(0); + expect(filterInput.val()).toEqual(''); + }); + }); + + describe('createNewItemFromValue option', () => { + beforeEach(() => { + createItemDropdown = new CreateItemDropdown({ + $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'), + defaultToggleLabel: 'All variables', + fieldName: 'variable[environment]', + getData: (term, callback) => { + callback(DROPDOWN_ITEM_DATA); + }, + createNewItemFromValue: newValue => ({ + title: `${newValue}-title`, + id: `${newValue}-id`, + text: `${newValue}-text`, + }), + }); + }); + + it('all items go through createNewItemFromValue', () => { + // Get the data in the dropdown + $('.js-dropdown-menu-toggle').click(); + + createItemAndClearInput('new-item'); + + const $itemEls = $wrapperEl.find('.js-dropdown-content a'); + expect($itemEls.length).toEqual(1 + DROPDOWN_ITEM_DATA.length); + expect($($itemEls[3]).text()).toEqual('new-item-text'); + expect($wrapperEl.find('.dropdown-toggle-text').text()).toEqual('new-item-title'); + }); + }); }); -- cgit v1.2.1 From dc996051142c2888d69d97e58e17ce5a009937c6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Jan 2018 10:57:00 +0000 Subject: cache jQuery selector --- app/assets/javascripts/issuable_index.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index aafadcb7d4e..0683ca82a38 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -23,21 +23,23 @@ export default class IssuableIndex { } static resetIncomingEmailToken() { - $('.incoming-email-token-reset').on('click', (e) => { + const $resetToken = $('.incoming-email-token-reset'); + + $resetToken.on('click', (e) => { e.preventDefault(); - $('.incoming-email-token-reset').text('resetting...'); + $resetToken.text('resetting...'); - axios.put($('.incoming-email-token-reset').attr('href')) + axios.put($resetToken.attr('href')) .then(({ data }) => { $('#issuable_email').val(data.new_address).focus(); - $('.incoming-email-token-reset').text('reset it'); + $resetToken.text('reset it'); }) .catch(() => { flash(__('There was an error when reseting email token.')); - $('.incoming-email-token-reset').text('reset it'); + $resetToken.text('reset it'); }); }); } -- cgit v1.2.1 From dfc626059b33a20161cf4f33950188e9e632f3a6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 30 Jan 2018 12:19:50 +0000 Subject: Replace $.get in single file diff with axios --- app/assets/javascripts/single_file_diff.js | 26 +++++++++++++++----------- spec/features/expand_collapse_diffs_spec.rb | 7 ------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 95e51bc4e7a..48dd91bdf06 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -1,5 +1,8 @@ /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */ +import { __ } from './locale'; +import axios from './lib/utils/axios_utils'; +import createFlash from './flash'; import FilesCommentButton from './files_comment_button'; import imageDiffHelper from './image_diff/helpers/index'; import syntaxHighlight from './syntax_highlight'; @@ -60,30 +63,31 @@ export default class SingleFileDiff { getContentHTML(cb) { this.collapsedContent.hide(); this.loadingContent.show(); - $.get(this.diffForPath, (function(_this) { - return function(data) { - _this.loadingContent.hide(); + + axios.get(this.diffForPath) + .then(({ data }) => { + this.loadingContent.hide(); if (data.html) { - _this.content = $(data.html); - syntaxHighlight(_this.content); + this.content = $(data.html); + syntaxHighlight(this.content); } else { - _this.hasError = true; - _this.content = $(ERROR_HTML); + this.hasError = true; + this.content = $(ERROR_HTML); } - _this.collapsedContent.after(_this.content); + this.collapsedContent.after(this.content); if (typeof gl.diffNotesCompileComponents !== 'undefined') { gl.diffNotesCompileComponents(); } - const $file = $(_this.file); + const $file = $(this.file); FilesCommentButton.init($file); const canCreateNote = $file.closest('.files').is('[data-can-create-note]'); imageDiffHelper.initImageDiff($file[0], canCreateNote); if (cb) cb(); - }; - })(this)); + }) + .catch(createFlash(__('An error occurred while retrieving diff'))); } } diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 1dd7547a7fc..31862b2e8f4 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -112,13 +112,6 @@ feature 'Expand and collapse diffs', :js do wait_for_requests end - it 'makes a request to get the content' do - ajax_uris = evaluate_script('ajaxUris') - - expect(ajax_uris).not_to be_empty - expect(ajax_uris.first).to include('large_diff.md') - end - it 'shows the diff content' do expect(large_diff).to have_selector('.code') expect(large_diff).not_to have_selector('.nothing-here-block') -- cgit v1.2.1 From 38f300081714c03bca88d7d7a4633fbf5ff1f154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 30 Jan 2018 14:06:13 +0100 Subject: Port changes from gitlab-org/gitlab-ee!4064 to CE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/views/layouts/header/_new_dropdown.haml | 2 +- qa/qa.rb | 7 ++++++ qa/qa/factory/resource/issue.rb | 34 +++++++++++++++++++++++++ qa/qa/page/base.rb | 32 ++++++++++++++++++++++++ qa/qa/page/group/show.rb | 16 +++++++----- qa/qa/page/menu/side.rb | 8 ++++++ qa/qa/page/project/issue/index.rb | 17 +++++++++++++ qa/qa/page/project/issue/new.rb | 33 +++++++++++++++++++++++++ qa/qa/page/project/issue/show.rb | 37 ++++++++++++++++++++++++++++ qa/qa/page/project/show.rb | 11 +++++++++ qa/spec/fixtures/banana_sample.gif | Bin 0 -> 71759 bytes 11 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 qa/qa/factory/resource/issue.rb create mode 100644 qa/qa/page/project/issue/index.rb create mode 100644 qa/qa/page/project/issue/new.rb create mode 100644 qa/qa/page/project/issue/show.rb create mode 100644 qa/spec/fixtures/banana_sample.gif diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 088f2785092..eb32f393310 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -1,5 +1,5 @@ %li.header-new.dropdown - = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do + = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do = sprite_icon('plus-square', size: 16) = sprite_icon('angle-down', css_class: 'caret-down') .dropdown-menu-nav.dropdown-menu-align-right diff --git a/qa/qa.rb b/qa/qa.rb index 180ee778fd4..bd24f241747 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -27,6 +27,7 @@ module QA module Resource autoload :Sandbox, 'qa/factory/resource/sandbox' autoload :Group, 'qa/factory/resource/group' + autoload :Issue, 'qa/factory/resource/issue' autoload :Project, 'qa/factory/resource/project' autoload :MergeRequest, 'qa/factory/resource/merge_request' autoload :DeployKey, 'qa/factory/resource/deploy_key' @@ -125,6 +126,12 @@ module QA autoload :SecretVariables, 'qa/page/project/settings/secret_variables' autoload :Runners, 'qa/page/project/settings/runners' end + + module Issue + autoload :New, 'qa/page/project/issue/new' + autoload :Show, 'qa/page/project/issue/show' + autoload :Index, 'qa/page/project/issue/index' + end end module Profile diff --git a/qa/qa/factory/resource/issue.rb b/qa/qa/factory/resource/issue.rb new file mode 100644 index 00000000000..06e7e8df56c --- /dev/null +++ b/qa/qa/factory/resource/issue.rb @@ -0,0 +1,34 @@ +require 'securerandom' + +module QA + module Factory + module Resource + class Issue < Factory::Base + attr_writer :title, :description, :project + + dependency Factory::Resource::Project, as: :project do |project| + project.name = 'project-for-issues' + project.description = 'project for adding issues' + end + + product :title do + Page::Project::Issue::Show.act { issue_title } + end + + def fabricate! + project.visit! + + Page::Project::Show.act do + go_to_new_issue + end + + Page::Project::Issue::New.perform do |page| + page.add_title(@title) + page.add_description(@description) + page.create_new_issue + end + end + end + end + end +end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index f472e8ccc7e..7a2d9731205 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -42,6 +42,23 @@ module QA page.within(selector) { yield } if block_given? end + # Returns true if successfully GETs the given URL + # Useful because `page.status_code` is unsupported by our driver, and + # we don't have access to the `response` to use `have_http_status`. + def asset_exists?(url) + page.execute_script <<~JS + xhr = new XMLHttpRequest(); + xhr.open('GET', '#{url}', true); + xhr.send(); + JS + + return false unless wait(time: 0.5, max: 60, reload: false) do + page.evaluate_script('xhr.readyState == XMLHttpRequest.DONE') + end + + page.evaluate_script('xhr.status') == 200 + end + def find_element(name) find(element_selector_css(name)) end @@ -80,6 +97,21 @@ module QA views.map(&:errors).flatten end + # Not tested and not expected to work with multiple dropzones + # instantiated on one page because there is no distinguishing + # attribute per dropzone file field. + def attach_file_to_dropzone(attachment, dropzone_form_container) + filename = File.basename(attachment) + + field_style = { visibility: 'visible', height: '', width: '' } + attach_file(attachment, class: 'dz-hidden-input', make_visible: field_style) + + # Wait for link to be appended to dropzone text + wait(reload: false) do + find("#{dropzone_form_container} textarea").value.match(filename) + end + end + class DSL attr_reader :views diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb index f23294145dd..f24859f2b1d 100644 --- a/qa/qa/page/group/show.rb +++ b/qa/qa/page/group/show.rb @@ -2,12 +2,16 @@ module QA module Page module Group class Show < Page::Base - ## - # TODO, define all selectors required by this page object - # - # See gitlab-org/gitlab-qa#154 - # - view 'app/views/groups/show.html.haml' + view 'app/views/groups/show.html.haml' do + element :new_project_or_subgroup_dropdown, '.new-project-subgroup' + element :new_project_or_subgroup_dropdown_toggle, '.dropdown-toggle' + element :new_project_option, /%li.*data:.*value: "new-project"/ + element :new_project_button, /%input.*data:.*action: "new-project"/ + element :new_subgroup_option, /%li.*data:.*value: "new-subgroup"/ + + # data-value and data-action get modified by JS for subgroup + element :new_subgroup_button, /%input.*\.js-new-group-child/ + end def go_to_subgroup(name) click_link name diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index b2738152907..5fdcea20029 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -7,6 +7,8 @@ module QA element :settings_link, 'link_to edit_project_path' element :repository_link, "title: 'Repository'" element :pipelines_settings_link, "title: 'CI / CD'" + element :issues_link, %r{link_to.*shortcuts-issues} + element :issues_link_text, "Issues" element :top_level_items, '.sidebar-top-level-items' element :activity_link, "title: 'Activity'" end @@ -43,6 +45,12 @@ module QA end end + def click_issues + within_sidebar do + click_link('Issues') + end + end + private def hover_settings diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb new file mode 100644 index 00000000000..b5903f536a4 --- /dev/null +++ b/qa/qa/page/project/issue/index.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Project + module Issue + class Index < Page::Base + view 'app/views/projects/issues/_issue.html.haml' do + element :issue_link, 'link_to issue.title' + end + + def go_to_issue(title) + click_link(title) + end + end + end + end + end +end diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb new file mode 100644 index 00000000000..7fc581da1ed --- /dev/null +++ b/qa/qa/page/project/issue/new.rb @@ -0,0 +1,33 @@ +module QA + module Page + module Project + module Issue + class New < Page::Base + view 'app/views/shared/issuable/_form.html.haml' do + element :submit_issue_button, 'form.submit "Submit' + end + + view 'app/views/shared/issuable/form/_title.html.haml' do + element :issue_title_textbox, 'form.text_field :title' + end + + view 'app/views/shared/form_elements/_description.html.haml' do + element :issue_description_textarea, "render 'projects/zen', f: form, attr: :description" + end + + def add_title(title) + fill_in 'issue_title', with: title + end + + def add_description(description) + fill_in 'issue_description', with: description + end + + def create_new_issue + click_on 'Submit issue' + end + end + end + end + end +end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb new file mode 100644 index 00000000000..10644c0fecc --- /dev/null +++ b/qa/qa/page/project/issue/show.rb @@ -0,0 +1,37 @@ +module QA + module Page + module Project + module Issue + class Show < Page::Base + view 'app/views/projects/issues/show.html.haml' do + element :issue_details, '.issue-details' + element :title, '.title' + end + + view 'app/views/shared/notes/_form.html.haml' do + element :new_note_form, 'new-note' + element :new_note_form, 'attr: :note' + end + + view 'app/views/shared/notes/_comment_button.html.haml' do + element :comment_button, '%strong Comment' + end + + def issue_title + find('.issue-details .title').text + end + + # Adds a comment to an issue + # attachment option should be an absolute path + def comment(text, attachment:) + fill_in(with: text, name: 'note[note]') + + attach_file_to_dropzone(attachment, '.new-note') if attachment + + click_on 'Comment' + end + end + end + end + end +end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 75308ae8a3c..553d35f9579 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -17,6 +17,11 @@ module QA element :project_name end + view 'app/views/layouts/header/_new_dropdown.haml' do + element :new_menu_toggle + element :new_issue_link, "link_to 'New issue', new_project_issue_path(@project)" + end + def choose_repository_clone_http wait(reload: false) do click_element :clone_dropdown @@ -46,6 +51,12 @@ module QA sleep 5 refresh end + + def go_to_new_issue + click_element :new_menu_toggle + + click_link 'New issue' + end end end end diff --git a/qa/spec/fixtures/banana_sample.gif b/qa/spec/fixtures/banana_sample.gif new file mode 100644 index 00000000000..1322ac92d14 Binary files /dev/null and b/qa/spec/fixtures/banana_sample.gif differ -- cgit v1.2.1