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 9fe6cabc993b9fb876875ef6b1058b4c6471c939 Mon Sep 17 00:00:00 2001
From: Melroy van den Berg
Date: Sun, 21 Jan 2018 16:39:45 +0000
Subject: Add omnibus remarks
---
lib/support/nginx/gitlab | 4 ++++
lib/support/nginx/gitlab-ssl | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 54f51d9d633..0e27a28ea6e 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -17,6 +17,8 @@
## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab-workhorse {
+ # Gitlab socket file,
+ # for Omnibus this would be: unix:/var/opt/gitlab/gitlab-workhorse/socket
server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
}
@@ -110,6 +112,8 @@ server {
error_page 502 /502.html;
error_page 503 /503.html;
location ~ ^/(404|422|500|502|503)\.html$ {
+ # Location to the Gitlab's public directory,
+ # for Omnibus this would be: /opt/gitlab/embedded/service/gitlab-rails/public.
root /home/git/gitlab/public;
internal;
}
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index ed8131ef24f..8218d68f9ba 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -21,6 +21,8 @@
## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab-workhorse {
+ # Gitlab socket file,
+ # for Omnibus this would be: unix:/var/opt/gitlab/gitlab-workhorse/socket
server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
}
@@ -160,6 +162,8 @@ server {
error_page 502 /502.html;
error_page 503 /503.html;
location ~ ^/(404|422|500|502|503)\.html$ {
+ # Location to the Gitlab's public directory,
+ # for Omnibus this would be: /opt/gitlab/embedded/service/gitlab-rails/public
root /home/git/gitlab/public;
internal;
}
--
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 ac3d56e0ffde27cdcf6515a9af4930324ebdf151 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin
Date: Wed, 24 Jan 2018 01:21:57 +0800
Subject: Pass BUNDLE_PATH to install to the same path for test
---
lib/tasks/gitlab/gitaly.rake | 6 +++++-
spec/tasks/gitlab/gitaly_rake_spec.rb | 6 +++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index a2e68c0471b..aece8893974 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -21,7 +21,11 @@ namespace :gitlab do
_, status = Gitlab::Popen.popen(%w[which gmake])
command << (status.zero? ? 'gmake' : 'make')
- command << 'BUNDLE_FLAGS=--no-deployment' if Rails.env.test?
+ if Rails.env.test?
+ command.push(
+ 'BUNDLE_FLAGS=--no-deployment',
+ "BUNDLE_PATH=#{Bundler.bundle_path}")
+ end
Gitlab::SetupHelper.create_gitaly_configuration(args.dir)
Dir.chdir(args.dir) do
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 6aba86fdc3c..b37d6ac831f 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -76,7 +76,11 @@ describe 'gitlab:gitaly namespace rake task' do
end
context 'when Rails.env is test' do
- let(:command) { %w[make BUNDLE_FLAGS=--no-deployment] }
+ let(:command) do
+ %W[make
+ BUNDLE_FLAGS=--no-deployment
+ BUNDLE_PATH=#{Bundler.bundle_path}]
+ end
before do
allow(Rails.env).to receive(:test?).and_return(true)
--
cgit v1.2.1
From 3a1e06d6a2494af5449abfee9fb49d56a0f3fe00 Mon Sep 17 00:00:00 2001
From: Felipe Artur
Date: Fri, 15 Dec 2017 14:59:35 -0200
Subject: Create merge request spec
---
app/views/projects/_last_push.html.haml | 2 +-
.../shared/form_elements/_description.html.haml | 2 +-
app/views/shared/issuable/_form.html.haml | 2 +-
app/views/shared/issuable/form/_title.html.haml | 2 +-
qa/qa.rb | 5 +++
qa/qa/factory/dependency.rb | 7 ++--
qa/qa/factory/resource/merge_request.rb | 49 ++++++++++++++++++++++
qa/qa/page/base.rb | 8 +++-
qa/qa/page/merge_request/new.rb | 31 ++++++++++++++
qa/qa/page/project/show.rb | 8 ++++
qa/qa/specs/features/merge_request/create_spec.rb | 17 ++++++++
qa/spec/factory/dependency_spec.rb | 13 ++++++
12 files changed, 137 insertions(+), 9 deletions(-)
create mode 100644 qa/qa/factory/resource/merge_request.rb
create mode 100644 qa/qa/page/merge_request/new.rb
create mode 100644 qa/qa/specs/features/merge_request/create_spec.rb
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 56eecece54c..6f5eb828902 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -14,5 +14,5 @@
#{time_ago_with_tooltip(event.created_at)}
.pull-right
- = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
+ = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
#{ _('Create merge request') }
diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index f65bb6a29e6..38e9899ca4b 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -15,7 +15,7 @@
= render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
= render 'projects/zen', f: form, attr: :description,
- classes: 'note-textarea',
+ classes: 'note-textarea qa-issuable-form-description',
placeholder: "Write a comment or drag your files here...",
supports_quick_actions: supports_quick_actions
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index bb02dfa0d3a..79021a08719 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -65,7 +65,7 @@
%span.append-right-10
- if issuable.new_record?
- = form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+ = form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create qa-issuable-create-button'
- else
= form.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml
index 64826d41d60..e81639f35ea 100644
--- a/app/views/shared/issuable/form/_title.html.haml
+++ b/app/views/shared/issuable/form/_title.html.haml
@@ -6,7 +6,7 @@
%div{ class: div_class }
= form.text_field :title, required: true, maxlength: 255, autofocus: true,
- autocomplete: 'off', class: 'form-control pad'
+ autocomplete: 'off', class: 'form-control pad qa-issuable-form-title'
- if issuable.respond_to?(:work_in_progress?)
%p.help-block
diff --git a/qa/qa.rb b/qa/qa.rb
index 5587a665809..529e3a05523 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -28,6 +28,7 @@ module QA
autoload :Sandbox, 'qa/factory/resource/sandbox'
autoload :Group, 'qa/factory/resource/group'
autoload :Project, 'qa/factory/resource/project'
+ autoload :MergeRequest, 'qa/factory/resource/merge_request'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
@@ -125,6 +126,10 @@ module QA
autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens'
end
+ module MergeRequest
+ autoload :New, 'qa/page/merge_request/new'
+ end
+
module Admin
autoload :Settings, 'qa/page/admin/settings'
end
diff --git a/qa/qa/factory/dependency.rb b/qa/qa/factory/dependency.rb
index d0e85a68237..fc5dc82ce29 100644
--- a/qa/qa/factory/dependency.rb
+++ b/qa/qa/factory/dependency.rb
@@ -16,20 +16,21 @@ module QA
def build!
return if overridden?
- Builder.new(@signature).fabricate!.tap do |product|
+ Builder.new(@signature, @factory).fabricate!.tap do |product|
@factory.public_send("#{@name}=", product)
end
end
class Builder
- def initialize(signature)
+ def initialize(signature, caller_factory)
@factory = signature.factory
@block = signature.block
+ @caller_factory = caller_factory
end
def fabricate!
@factory.fabricate! do |factory|
- @block&.call(factory)
+ @block&.call(factory, @caller_factory)
end
end
end
diff --git a/qa/qa/factory/resource/merge_request.rb b/qa/qa/factory/resource/merge_request.rb
new file mode 100644
index 00000000000..ce04e904aaf
--- /dev/null
+++ b/qa/qa/factory/resource/merge_request.rb
@@ -0,0 +1,49 @@
+require 'securerandom'
+
+module QA
+ module Factory
+ module Resource
+ class MergeRequest < Factory::Base
+ attr_accessor :title,
+ :description,
+ :source_branch,
+ :target_branch
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-with-merge-request'
+ end
+
+ dependency Factory::Repository::Push, as: :target do |push, factory|
+ push.project = factory.project
+ push.branch_name = "master:#{factory.target_branch}"
+ end
+
+ dependency Factory::Repository::Push, as: :source do |push, factory|
+ push.project = factory.project
+ push.branch_name = "#{factory.target_branch}:#{factory.source_branch}"
+ push.file_name = "added_file.txt"
+ push.file_content = "File Added"
+ end
+
+ def initialize
+ @title = 'QA test - merge request'
+ @description = 'This is a test merge request'
+ @source_branch = "qa-test-feature-#{SecureRandom.hex(8)}"
+ @target_branch = "master"
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Show.act { new_merge_request }
+
+ Page::MergeRequest::New.perform do |page|
+ page.fill_title(@title)
+ page.fill_description(@description)
+ page.create_merge_request
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 81ba80cdbaf..73aa23262da 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -40,12 +40,16 @@ module QA
page.within(selector) { yield } if block_given?
end
+ def find_element(name)
+ find(element_selector_css(name))
+ end
+
def click_element(name)
find_element(name).click
end
- def find_element(name)
- find(element_selector_css(name))
+ def fill_element(name, content)
+ find_element(name).set(content)
end
def within_element(name)
diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb
new file mode 100644
index 00000000000..ec94ff4ac98
--- /dev/null
+++ b/qa/qa/page/merge_request/new.rb
@@ -0,0 +1,31 @@
+module QA
+ module Page
+ module MergeRequest
+ class New < Page::Base
+ view 'app/views/shared/issuable/_form.html.haml' do
+ element :issuable_create_button
+ end
+
+ view 'app/views/shared/issuable/form/_title.html.haml' do
+ element :issuable_form_title
+ end
+
+ view 'app/views/shared/form_elements/_description.html.haml' do
+ element :issuable_form_description
+ end
+
+ def create_merge_request
+ click_element :issuable_create_button
+ end
+
+ def fill_title(title)
+ fill_element :issuable_form_title, title
+ end
+
+ def fill_description(description)
+ fill_element :issuable_form_description, description
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 5e66e40a0b5..24eb29ceb4c 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -11,6 +11,10 @@ module QA
element :project_repository_location, 'text_field_tag :project_clone'
end
+ view 'app/views/projects/_last_push.html.haml' do
+ element :create_merge_request
+ end
+
view 'app/views/projects/_home_panel.html.haml' do
element :project_name
end
@@ -31,6 +35,10 @@ module QA
find('.qa-project-name').text
end
+ def new_merge_request
+ click_element :create_merge_request
+ end
+
def wait_for_push
sleep 5
refresh
diff --git a/qa/qa/specs/features/merge_request/create_spec.rb b/qa/qa/specs/features/merge_request/create_spec.rb
new file mode 100644
index 00000000000..fbf9a4d17e5
--- /dev/null
+++ b/qa/qa/specs/features/merge_request/create_spec.rb
@@ -0,0 +1,17 @@
+module QA
+ feature 'creates a merge request', :core do
+ scenario 'user creates a new merge request' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ Factory::Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.title = 'This is a merge request'
+ merge_request.description = 'Great feature'
+ end
+
+ expect(page).to have_content('This is a merge request')
+ expect(page).to have_content('Great feature')
+ expect(page).to have_content('Opened less than a minute ago')
+ end
+ end
+end
diff --git a/qa/spec/factory/dependency_spec.rb b/qa/spec/factory/dependency_spec.rb
index 32405415126..8aaa6665a18 100644
--- a/qa/spec/factory/dependency_spec.rb
+++ b/qa/spec/factory/dependency_spec.rb
@@ -54,6 +54,19 @@ describe QA::Factory::Dependency do
expect(factory).to have_received(:mydep=).with(dependency)
end
+
+ context 'when receives a caller factory as block argument' do
+ let(:dependency) { QA::Factory::Base }
+
+ it 'calls given block with dependency factory and caller factory' do
+ allow_any_instance_of(QA::Factory::Base).to receive(:fabricate!).and_return(factory)
+ allow(QA::Factory::Product).to receive(:populate!).and_return(spy('any'))
+
+ subject.build!
+
+ expect(block).to have_received(:call).with(an_instance_of(QA::Factory::Base), factory)
+ end
+ end
end
end
end
--
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 828e1573a722e8254037041eaccd3ae2bf6f9e60 Mon Sep 17 00:00:00 2001
From: Nick Thomas
Date: Fri, 26 Jan 2018 14:26:04 +0000
Subject: Fix JS equality rules in some specs
---
spec/javascripts/pipelines/pipelines_table_row_spec.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index b3cbf9aba48..de744739e42 100644
--- a/spec/javascripts/pipelines/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -26,8 +26,8 @@ describe('Pipelines Table Row', () => {
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
- pipelineWithoutAuthor = pipelines.find(p => p.user == null && p.commit !== null);
- pipelineWithoutCommit = pipelines.find(p => p.user == null && p.commit == null);
+ pipelineWithoutAuthor = pipelines.find(p => p.user === null && p.commit !== null);
+ pipelineWithoutCommit = pipelines.find(p => p.user === null && p.commit === null);
});
afterEach(() => {
--
cgit v1.2.1
From 428db24a033ff9c145405286481d682701698a49 Mon Sep 17 00:00:00 2001
From: Michael Kozono
Date: Thu, 25 Jan 2018 22:35:06 -0800
Subject: Fix click on incorrect namespace
in certain conditions. And `first` was causing it to succeed even though the criteria were ambiguous.
---
qa/qa/page/project/new.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 9b1438f76d5..186a4724326 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -4,7 +4,7 @@ module QA
class New < Page::Base
view 'app/views/projects/_new_project_fields.html.haml' do
element :project_namespace_select
- element :project_namespace_field, 'select :namespace_id'
+ element :project_namespace_field, /select :namespace_id.*class: 'select2/
element :project_path, 'text_field :path'
element :project_description, 'text_area :description'
element :project_create_button, "submit 'Create project'"
@@ -13,7 +13,7 @@ module QA
def choose_test_namespace
click_element :project_namespace_select
- first('li', text: Runtime::Namespace.path).click
+ find('ul.select2-result-sub > li', text: Runtime::Namespace.path).click
end
def choose_name(name)
--
cgit v1.2.1
From 09a7a1f0767285d59855bc3722ed838672c78c7f Mon Sep 17 00:00:00 2001
From: Michael Kozono
Date: Thu, 25 Jan 2018 23:03:14 -0800
Subject: Fix intermittent click error
---
qa/qa/page/base.rb | 16 +++++++++-------
qa/qa/page/main/login.rb | 6 ++++--
qa/qa/page/project/settings/common.rb | 7 ++++++-
3 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 81ba80cdbaf..fa7a3760057 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -13,16 +13,18 @@ module QA
visit current_url
end
- def wait(css = '.application', time: 60)
- Time.now.tap do |start|
- while Time.now - start < time
- break if page.has_css?(css, wait: 5)
+ def wait(max: 60, time: 1, reload: true)
+ start = Time.now
- refresh
- end
+ while Time.now - start < max
+ return true if yield
+
+ sleep(time)
+
+ refresh if reload
end
- yield if block_given?
+ false
end
def scroll_to(selector, text: nil)
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 9cff2c5c317..95880475ffa 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -10,12 +10,14 @@ module QA
view 'app/views/devise/sessions/_new_base.html.haml' do
element :login_field, 'text_field :login'
- element :passowrd_field, 'password_field :password'
+ element :password_field, 'password_field :password'
element :sign_in_button, 'submit "Sign in"'
end
def initialize
- wait('.application', time: 500)
+ wait(max: 500) do
+ page.has_css?('.application')
+ end
end
def sign_in_using_credentials
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index c7955124ef3..319cb1045b6 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -17,7 +17,12 @@ module QA
def expand_section(name)
page.within('#content-body') do
page.within('section', text: name) do
- click_button 'Expand' unless first('button', text: 'Collapse')
+ # Because it is possible to click the button before the JS toggle code is bound
+ wait(reload: false) do
+ click_button 'Expand' unless first('button', text: 'Collapse')
+
+ page.has_content?('Collapse')
+ end
yield if block_given?
end
--
cgit v1.2.1
From a1a458279489e6b95209a55f1878642d53218684 Mon Sep 17 00:00:00 2001
From: Michael Kozono
Date: Thu, 25 Jan 2018 23:15:33 -0800
Subject: Fix intermittently failed click in dropdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
I haven’t seen `go_to_new_subgroup` fail in the same way, but it seems best to modify it also.
---
qa/qa/page/base.rb | 16 +++++++++-------
qa/qa/page/group/show.rb | 14 ++++++++++++--
qa/qa/page/main/login.rb | 6 ++++--
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 81ba80cdbaf..fa7a3760057 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -13,16 +13,18 @@ module QA
visit current_url
end
- def wait(css = '.application', time: 60)
- Time.now.tap do |start|
- while Time.now - start < time
- break if page.has_css?(css, wait: 5)
+ def wait(max: 60, time: 1, reload: true)
+ start = Time.now
- refresh
- end
+ while Time.now - start < max
+ return true if yield
+
+ sleep(time)
+
+ refresh if reload
end
- yield if block_given?
+ false
end
def scroll_to(selector, text: nil)
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 37ed3b35bce..f23294145dd 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -25,7 +25,12 @@ module QA
def go_to_new_subgroup
within '.new-project-subgroup' do
- find('.dropdown-toggle').click
+ # May need to click again because it is possible to click the button quicker than the JS is bound
+ wait(reload: false) do
+ find('.dropdown-toggle').click
+
+ page.has_css?("li[data-value='new-subgroup']")
+ end
find("li[data-value='new-subgroup']").click
end
@@ -34,7 +39,12 @@ module QA
def go_to_new_project
within '.new-project-subgroup' do
- find('.dropdown-toggle').click
+ # May need to click again because it is possible to click the button quicker than the JS is bound
+ wait(reload: false) do
+ find('.dropdown-toggle').click
+
+ page.has_css?("li[data-value='new-project']")
+ end
find("li[data-value='new-project']").click
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 9cff2c5c317..95880475ffa 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -10,12 +10,14 @@ module QA
view 'app/views/devise/sessions/_new_base.html.haml' do
element :login_field, 'text_field :login'
- element :passowrd_field, 'password_field :password'
+ element :password_field, 'password_field :password'
element :sign_in_button, 'submit "Sign in"'
end
def initialize
- wait('.application', time: 500)
+ wait(max: 500) do
+ page.has_css?('.application')
+ end
end
def sign_in_using_credentials
--
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
-
- Remove Source Branch
-
-
-
-
- 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 @@
+
+
+
+
+
+
+
+
+ {{ s__("mrWidget|The changes were merged into") }}
+
+ {{ mr.targetBranch }}
+
+
+
+ {{ s__("mrWidget|The source branch has been removed") }}
+
+
+ {{ s__("mrWidget|You can remove source branch now") }}
+
+ {{ s__("mrWidget|Remove Source Branch") }}
+
+
+
+
+
+ {{ s__("mrWidget|The source branch is being removed") }}
+
+
+
+
+
+
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 8bdced4288cb3c7eaefbb8486bfc4af73d90b21e Mon Sep 17 00:00:00 2001
From: Stan Hu
Date: Fri, 26 Jan 2018 15:25:05 -0800
Subject: Mute notifications in the seeder to speed GDK development
---
lib/gitlab/seeder.rb | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 94a481a0f2e..98f005cb61b 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -5,9 +5,15 @@ module DeliverNever
end
end
+module MuteNotifications
+ def new_note(note)
+ end
+end
+
module Gitlab
class Seeder
def self.quiet
+ mute_notifications
mute_mailer
SeedFu.quiet = true
@@ -18,6 +24,10 @@ module Gitlab
puts "\nOK".color(:green)
end
+ def self.mute_notifications
+ NotificationService.prepend(MuteNotifications)
+ end
+
def self.mute_mailer
ActionMailer::MessageDelivery.prepend(DeliverNever)
end
--
cgit v1.2.1
From 25cd25fc40b3178530f62755158ecd047d8a0b49 Mon Sep 17 00:00:00 2001
From: Michael Kozono
Date: Fri, 26 Jan 2018 09:41:19 -0800
Subject: Fix intermittent clone over SSH failure
We always clone over HTTP, so if the QA spec attempts to clone over SSH, we know it gathered the wrong repo URI.
---
qa/qa/page/base.rb | 16 +++++++++-------
qa/qa/page/main/login.rb | 6 ++++--
qa/qa/page/project/show.rb | 15 +++++++++------
3 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 81ba80cdbaf..fa7a3760057 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -13,16 +13,18 @@ module QA
visit current_url
end
- def wait(css = '.application', time: 60)
- Time.now.tap do |start|
- while Time.now - start < time
- break if page.has_css?(css, wait: 5)
+ def wait(max: 60, time: 1, reload: true)
+ start = Time.now
- refresh
- end
+ while Time.now - start < max
+ return true if yield
+
+ sleep(time)
+
+ refresh if reload
end
- yield if block_given?
+ false
end
def scroll_to(selector, text: nil)
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 9cff2c5c317..95880475ffa 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -10,12 +10,14 @@ module QA
view 'app/views/devise/sessions/_new_base.html.haml' do
element :login_field, 'text_field :login'
- element :passowrd_field, 'password_field :password'
+ element :password_field, 'password_field :password'
element :sign_in_button, 'submit "Sign in"'
end
def initialize
- wait('.application', time: 500)
+ wait(max: 500) do
+ page.has_css?('.application')
+ end
end
def sign_in_using_credentials
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 5e66e40a0b5..5762c50cde7 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -3,11 +3,9 @@ module QA
module Project
class Show < Page::Base
view 'app/views/shared/_clone_panel.html.haml' do
+ element :clone_holder, '.git-clone-holder'
element :clone_dropdown
element :clone_options_dropdown, '.clone-options-dropdown'
- end
-
- view 'app/views/shared/_clone_panel.html.haml' do
element :project_repository_location, 'text_field_tag :project_clone'
end
@@ -16,10 +14,15 @@ module QA
end
def choose_repository_clone_http
- click_element :clone_dropdown
+ wait(reload: false) do
+ click_element :clone_dropdown
+
+ page.within('.clone-options-dropdown') do
+ click_link('HTTP')
+ end
- page.within('.clone-options-dropdown') do
- click_link('HTTP')
+ # Ensure git clone textbox was updated to http URI
+ page.has_css?('.git-clone-holder input#project_clone[value*="http"]')
end
end
--
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 94039bd7ca750446c637b924547493cfe8143716 Mon Sep 17 00:00:00 2001
From: Felix Geyer
Date: Tue, 23 Jan 2018 23:25:44 +0100
Subject: Make default_avatar return a full path.
Callers expect a URL.
This fixes the avatars in commit lists when a user has no avatar and
Gravatar is disabled.
Closes #38715
---
app/helpers/application_helper.rb | 2 +-
spec/helpers/application_helper_spec.rb | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d13407a06c8..6530327698b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -89,7 +89,7 @@ module ApplicationHelper
end
def default_avatar
- 'no_avatar.png'
+ asset_path('no_avatar.png')
end
def last_commit(project)
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index da0343588ef..f7a4a7afced 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -100,7 +100,7 @@ describe ApplicationHelper do
end
it 'returns a generic avatar' do
- expect(helper.gravatar_icon(user_email)).to match('no_avatar.png')
+ expect(helper.gravatar_icon(user_email)).to match_asset_path('no_avatar.png')
end
end
@@ -110,7 +110,7 @@ describe ApplicationHelper do
end
it 'returns a generic avatar when email is blank' do
- expect(helper.gravatar_icon('')).to match('no_avatar.png')
+ expect(helper.gravatar_icon('')).to match_asset_path('no_avatar.png')
end
it 'returns a valid Gravatar URL' do
--
cgit v1.2.1
From 65497b5f65ac115bb0219e83523f192fd51c91f2 Mon Sep 17 00:00:00 2001
From: Felix Geyer
Date: Tue, 23 Jan 2018 23:29:20 +0100
Subject: Fall back to default_avatar_url in issue assigment.
This fixes the avatar in issue assignments when the user hasn't got an
avatar and Gravatar is disabled.
Closes #34733
---
app/assets/javascripts/sidebar/components/assignees/assignees.js | 2 +-
app/assets/javascripts/users_select.js | 6 ++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.js b/app/assets/javascripts/sidebar/components/assignees/assignees.js
index 7e5feac622c..643877b9d47 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.js
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.js
@@ -84,7 +84,7 @@ export default {
return !this.showLess || (index < this.defaultRenderCount && this.showLess);
},
avatarUrl(user) {
- return user.avatar || user.avatar_url;
+ return user.avatar || user.avatar_url || gon.default_avatar_url;
},
assigneeUrl(user) {
return `${this.rootPath}${user.username}`;
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index f249bd036d6..ab108906732 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -492,7 +492,7 @@ function UsersSelect(currentUser, els, options = {}) {
renderRow: function(user) {
var avatar, img, listClosingTags, listWithName, listWithUserName, username;
username = user.username ? "@" + user.username : "";
- avatar = user.avatar_url ? user.avatar_url : false;
+ avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
let selected = false;
@@ -513,9 +513,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (user.beforeDivider != null) {
`${_.escape(user.name)} `;
} else {
- if (avatar) {
- img = " ";
- }
+ img = " ";
}
return `
--
cgit v1.2.1
From ed341db24fac8ce62513c3953da8097ba8920271 Mon Sep 17 00:00:00 2001
From: Felix Geyer
Date: Wed, 24 Jan 2018 17:49:08 +0100
Subject: Add changelog entry for !16681
---
.../unreleased/34733-fix-default-avatar-when-gravatar-disabled.yml | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 changelogs/unreleased/34733-fix-default-avatar-when-gravatar-disabled.yml
diff --git a/changelogs/unreleased/34733-fix-default-avatar-when-gravatar-disabled.yml b/changelogs/unreleased/34733-fix-default-avatar-when-gravatar-disabled.yml
new file mode 100644
index 00000000000..0791847b64d
--- /dev/null
+++ b/changelogs/unreleased/34733-fix-default-avatar-when-gravatar-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Fix default avatar icon missing when Gravatar is disabled
+merge_request: 16681
+author: Felix Geyer
+type: fixed
--
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 7e8d5e83aa1f4b4f268f8ec413bdad53e44daada Mon Sep 17 00:00:00 2001
From: astrachan
Date: Mon, 29 Jan 2018 11:35:59 +1000
Subject: Updating docs for usage of force=yes when performing a restore.
---
doc/raketasks/backup_restore.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 76f33b765d3..85db8f40a28 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -494,7 +494,7 @@ more of the following options:
- `BACKUP=timestamp_of_backup` - Required if more than one backup exists.
Read what the [backup timestamp is about](#backup-timestamp).
-- `force=yes` - Do not ask if the authorized_keys file should get regenerated.
+- `force=yes` - Does not ask if the authorized_keys file should get regenerated and assumes 'yes' for warning that database tables will be removed.
### Restore for installation from source
--
cgit v1.2.1
From e0c363ca01575d9d5d04b3fe285d399d69d5a3ca Mon Sep 17 00:00:00 2001
From: Stan Hu
Date: Fri, 26 Jan 2018 14:22:22 -0800
Subject: Digital Ocean Spaces now supports AWS v4 streaming API
See https://github.com/fog/fog-aws/issues/428#issuecomment-360869183
---
doc/raketasks/backup_restore.md | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 76f33b765d3..24d678d5cde 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -169,15 +169,11 @@ For Omnibus GitLab packages:
1. [Reconfigure GitLab] for the changes to take effect
-#### Digital Ocean Spaces and other S3-compatible providers
+#### Digital Ocean Spaces
-Not all S3 providers are fully-compatible with the Fog library. For example,
-if you see `411 Length Required` errors after attempting to upload, you may
-need to downgrade the `aws_signature_version` value from the default value to
-2 [due to this issue](https://github.com/fog/fog-aws/issues/428).
+This example can be used for a bucket in Amsterdam (AMS3).
-1. For example, with [Digital Ocean Spaces](https://www.digitalocean.com/products/spaces/),
-this example configuration can be used for a bucket in Amsterdam (AMS3):
+1. Add the following to `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['backup_upload_connection'] = {
@@ -185,7 +181,6 @@ this example configuration can be used for a bucket in Amsterdam (AMS3):
'region' => 'ams3',
'aws_access_key_id' => 'AKIAKIAKI',
'aws_secret_access_key' => 'secret123',
- 'aws_signature_version' => 2,
'endpoint' => 'https://ams3.digitaloceanspaces.com'
}
gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
@@ -193,6 +188,13 @@ this example configuration can be used for a bucket in Amsterdam (AMS3):
1. [Reconfigure GitLab] for the changes to take effect
+#### Other S3 Providers
+
+Not all S3 providers are fully-compatible with the Fog library. For example,
+if you see `411 Length Required` errors after attempting to upload, you may
+need to downgrade the `aws_signature_version` value from the default value to
+2 [due to this issue](https://github.com/fog/fog-aws/issues/428).
+
---
For installations from source:
--
cgit v1.2.1
From 3f79376c76c6e788031362926d769351bc55c7a3 Mon Sep 17 00:00:00 2001
From: "Jacob Vosmaer (GitLab)"
Date: Mon, 29 Jan 2018 08:51:25 +0000
Subject: Set alternate object directories in run_git
---
lib/gitlab/git/blame.rb | 4 +---
lib/gitlab/git/popen.rb | 2 ++
lib/gitlab/git/repository.rb | 52 +++++++++++++++++++++++++++-----------------
3 files changed, 35 insertions(+), 23 deletions(-)
diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb
index 31effdba292..6d6ed065f79 100644
--- a/lib/gitlab/git/blame.rb
+++ b/lib/gitlab/git/blame.rb
@@ -42,9 +42,7 @@ module Gitlab
end
def load_blame_by_shelling_out
- cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path})
- # Read in binary mode to ensure ASCII-8BIT
- IO.popen(cmd, 'rb') {|io| io.read }
+ @repo.shell_blame(@sha, @path)
end
def process_raw_blame(output)
diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb
index 1ccca13ce2f..e0bd2bbe47b 100644
--- a/lib/gitlab/git/popen.rb
+++ b/lib/gitlab/git/popen.rb
@@ -19,6 +19,8 @@ module Gitlab
cmd_output = ""
cmd_status = 0
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ stdout.set_encoding(Encoding::ASCII_8BIT)
+
yield(stdin) if block_given?
stdin.close
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 638d335b523..64b491517cb 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -614,11 +614,11 @@ module Gitlab
if is_enabled
gitaly_ref_client.find_ref_name(sha, ref_path)
else
- args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
+ args = %W(for-each-ref --count=1 #{ref_path} --contains #{sha})
# Not found -> ["", 0]
# Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]
- popen(args, @path).first.split.last
+ run_git(args).first.split.last
end
end
end
@@ -887,8 +887,7 @@ module Gitlab
"delete #{ref}\x00\x00"
end
- command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
- message, status = popen(command, path) do |stdin|
+ message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
stdin.write(instructions.join)
end
@@ -1409,6 +1408,11 @@ module Gitlab
end
end
+ def shell_blame(sha, path)
+ output, _status = run_git(%W(blame -p #{sha} -- #{path}))
+ output
+ end
+
private
def shell_write_ref(ref_path, ref, old_ref)
@@ -1433,6 +1437,12 @@ module Gitlab
def run_git(args, chdir: path, env: {}, nice: false, &block)
cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice
+
+ object_directories = alternate_object_directories
+ if object_directories.any?
+ env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = object_directories.join(File::PATH_SEPARATOR)
+ end
+
circuit_breaker.perform do
popen(cmd, chdir, env, &block)
end
@@ -1624,7 +1634,7 @@ module Gitlab
offset_in_ruby = use_follow_flag && options[:offset].present?
limit += offset if offset_in_ruby
- cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log]
+ cmd = %w[log]
cmd << "--max-count=#{limit}"
cmd << '--format=%H'
cmd << "--skip=#{offset}" unless offset_in_ruby
@@ -1640,7 +1650,7 @@ module Gitlab
cmd += Array(options[:path])
end
- raw_output = IO.popen(cmd) { |io| io.read }
+ raw_output, _status = run_git(cmd)
lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
@@ -1678,18 +1688,23 @@ module Gitlab
end
def alternate_object_directories
- relative_paths = Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
+ relative_paths = relative_object_directories
if relative_paths.any?
relative_paths.map { |d| File.join(path, d) }
else
- Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES)
- .flatten
- .compact
- .flat_map { |d| d.split(File::PATH_SEPARATOR) }
+ absolute_object_directories.flat_map { |d| d.split(File::PATH_SEPARATOR) }
end
end
+ def relative_object_directories
+ Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
+ end
+
+ def absolute_object_directories
+ Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).flatten.compact
+ end
+
# Get the content of a blob for a given commit. If the blob is a commit
# (for submodules) then return the blob's OID.
def blob_content(commit, blob_name)
@@ -1833,13 +1848,13 @@ module Gitlab
def count_commits_by_shelling_out(options)
cmd = count_commits_shelling_command(options)
- raw_output = IO.popen(cmd) { |io| io.read }
+ raw_output, _status = run_git(cmd)
process_count_commits_raw_output(raw_output, options)
end
def count_commits_shelling_command(options)
- cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list]
+ cmd = %w[rev-list]
cmd << "--after=#{options[:after].iso8601}" if options[:after]
cmd << "--before=#{options[:before].iso8601}" if options[:before]
cmd << "--max-count=#{options[:max_count]}" if options[:max_count]
@@ -1884,20 +1899,17 @@ module Gitlab
return []
end
- cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} ls-tree)
- cmd += %w(-r)
- cmd += %w(--full-tree)
- cmd += %w(--full-name)
- cmd += %W(-- #{actual_ref})
+ cmd = %W(ls-tree -r --full-tree --full-name -- #{actual_ref})
+ raw_output, _status = run_git(cmd)
- raw_output = IO.popen(cmd, &:read).split("\n").map do |f|
+ lines = raw_output.split("\n").map do |f|
stuff, path = f.split("\t")
_mode, type, _sha = stuff.split(" ")
path if type == "blob"
# Contain only blob type
end
- raw_output.compact
+ lines.compact
end
# Returns true if the given ref name exists
--
cgit v1.2.1
From 9846332fcd8661dee7f15da4e0f177dc027254cb Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 10:12:18 +0000
Subject: Convered compare.js to axios
---
app/assets/javascripts/compare.js | 47 +++++++++++++++++----------------------
1 file changed, 21 insertions(+), 26 deletions(-)
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index 144caf1d278..4c04fb40f31 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
import { localTimeAgo } from './lib/utils/datetime_utility';
+import axios from './lib/utils/axios_utils';
export default class Compare {
constructor(opts) {
@@ -41,17 +42,14 @@ export default class Compare {
}
getTargetProject() {
- return $.ajax({
- url: this.opts.targetProjectUrl,
- data: {
- target_project_id: $("input[name='merge_request[target_project_id]']").val()
- },
- beforeSend: function() {
- return $('.mr_target_commit').empty();
+ $('.mr_target_commit').empty();
+
+ return axios.get(this.opts.targetProjectUrl, {
+ params: {
+ target_project_id: $("input[name='merge_request[target_project_id]']").val(),
},
- success: function(html) {
- return $('.js-target-branch-dropdown .dropdown-content').html(html);
- }
+ }).then(({ data }) => {
+ $('.js-target-branch-dropdown .dropdown-content').html(data);
});
}
@@ -68,22 +66,19 @@ export default class Compare {
});
}
- static sendAjax(url, loading, target, data) {
- var $target;
- $target = $(target);
- return $.ajax({
- url: url,
- data: data,
- beforeSend: function() {
- loading.show();
- return $target.empty();
- },
- success: function(html) {
- loading.hide();
- $target.html(html);
- var className = '.' + $target[0].className.replace(' ', '.');
- localTimeAgo($('.js-timeago', className));
- }
+ static sendAjax(url, loading, target, params) {
+ const $target = $(target);
+
+ loading.show();
+ $target.empty();
+
+ return axios.get(url, {
+ params,
+ }).then(({ data }) => {
+ loading.hide();
+ $target.html(data);
+ var className = '.' + $target[0].className.replace(' ', '.');
+ localTimeAgo($('.js-timeago', className));
});
}
}
--
cgit v1.2.1
From 797f29d184827312fb4c91a7a0c22863f0870c4b Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 10:14:44 +0000
Subject: converted compare_autocomplete to axios
---
app/assets/javascripts/compare_autocomplete.js | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index e633ef8a29e..0787400e862 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
+import axios from './lib/utils/axios_utils';
+import flash from './flash';
export default function initCompareAutocomplete() {
$('.js-compare-dropdown').each(function() {
@@ -10,15 +12,14 @@ export default function initCompareAutocomplete() {
const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown.glDropdown({
data: function(term, callback) {
- return $.ajax({
- url: $dropdown.data('refs-url'),
- data: {
+ axios.get($dropdown.data('refsUrl'), {
+ params: {
ref: $dropdown.data('ref'),
search: term,
- }
- }).done(function(refs) {
- return callback(refs);
- });
+ },
+ }).then(({ data }) => {
+ callback(data);
+ }).catch(() => flash('Error fetching refs'));
},
selectable: true,
filterable: true,
--
cgit v1.2.1
From 6a89ce29533d43aa7c34fb2a5e54b9ff4048e73b Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 10:27:50 +0000
Subject: Converted create_merge_request_dropdown to axios
---
.../javascripts/create_merge_request_dropdown.js | 147 ++++++++++-----------
1 file changed, 67 insertions(+), 80 deletions(-)
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index bc23a72762f..482d83621e2 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
import _ from 'underscore';
+import axios from './lib/utils/axios_utils';
import Flash from './flash';
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
@@ -74,60 +75,52 @@ export default class CreateMergeRequestDropdown {
}
checkAbilityToCreateBranch() {
- return $.ajax({
- type: 'GET',
- dataType: 'json',
- url: this.canCreatePath,
- beforeSend: () => this.setUnavailableButtonState(),
- })
- .done((data) => {
- this.setUnavailableButtonState(false);
-
- if (data.can_create_branch) {
- this.available();
- this.enable();
-
- if (!this.droplabInitialized) {
- this.droplabInitialized = true;
- this.initDroplab();
- this.bindEvents();
+ this.setUnavailableButtonState();
+
+ axios.get(this.canCreatePath)
+ .then(({ data }) => {
+ this.setUnavailableButtonState(false);
+
+ if (data.can_create_branch) {
+ this.available();
+ this.enable();
+
+ if (!this.droplabInitialized) {
+ this.droplabInitialized = true;
+ this.initDroplab();
+ this.bindEvents();
+ }
+ } else if (data.has_related_branch) {
+ this.hide();
}
- } else if (data.has_related_branch) {
- this.hide();
- }
- }).fail(() => {
- this.unavailable();
- this.disable();
- new Flash('Failed to check if a new branch can be created.');
- });
+ })
+ .catch(() => {
+ this.unavailable();
+ this.disable();
+ Flash('Failed to check if a new branch can be created.');
+ });
}
createBranch() {
- return $.ajax({
- method: 'POST',
- dataType: 'json',
- url: this.createBranchPath,
- beforeSend: () => (this.isCreatingBranch = true),
- })
- .done((data) => {
- this.branchCreated = true;
- window.location.href = data.url;
- })
- .fail(() => new Flash('Failed to create a branch for this issue. Please try again.'));
+ this.isCreatingBranch = true;
+
+ return axios.post(this.createBranchPath)
+ .then(({ data }) => {
+ this.branchCreated = true;
+ window.location.href = data.url;
+ })
+ .catch(() => Flash('Failed to create a branch for this issue. Please try again.'));
}
createMergeRequest() {
- return $.ajax({
- method: 'POST',
- dataType: 'json',
- url: this.createMrPath,
- beforeSend: () => (this.isCreatingMergeRequest = true),
- })
- .done((data) => {
- this.mergeRequestCreated = true;
- window.location.href = data.url;
- })
- .fail(() => new Flash('Failed to create Merge Request. Please try again.'));
+ this.isCreatingMergeRequest = true;
+
+ return axios.post(this.createMrPath)
+ .then(({ data }) => {
+ this.mergeRequestCreated = true;
+ window.location.href = data.url;
+ })
+ .catch(() => Flash('Failed to create Merge Request. Please try again.'));
}
disable() {
@@ -200,39 +193,33 @@ export default class CreateMergeRequestDropdown {
getRef(ref, target = 'all') {
if (!ref) return false;
- return $.ajax({
- method: 'GET',
- dataType: 'json',
- url: this.refsPath + ref,
- beforeSend: () => {
- this.isGettingRef = true;
- },
- })
- .always(() => {
- this.isGettingRef = false;
- })
- .done((data) => {
- const branches = data[Object.keys(data)[0]];
- const tags = data[Object.keys(data)[1]];
- let result;
+ return axios.get(this.refsPath + ref)
+ .then(({ data }) => {
+ const branches = data[Object.keys(data)[0]];
+ const tags = data[Object.keys(data)[1]];
+ let result;
+
+ if (target === 'branch') {
+ result = CreateMergeRequestDropdown.findByValue(branches, ref);
+ } else {
+ result = CreateMergeRequestDropdown.findByValue(branches, ref, true) ||
+ CreateMergeRequestDropdown.findByValue(tags, ref, true);
+ this.suggestedRef = result;
+ }
- if (target === 'branch') {
- result = CreateMergeRequestDropdown.findByValue(branches, ref);
- } else {
- result = CreateMergeRequestDropdown.findByValue(branches, ref, true) ||
- CreateMergeRequestDropdown.findByValue(tags, ref, true);
- this.suggestedRef = result;
- }
+ this.isGettingRef = false;
- return this.updateInputState(target, ref, result);
- })
- .fail(() => {
- this.unavailable();
- this.disable();
- new Flash('Failed to get ref.');
+ return this.updateInputState(target, ref, result);
+ })
+ .catch(() => {
+ this.unavailable();
+ this.disable();
+ new Flash('Failed to get ref.');
- return false;
- });
+ this.isGettingRef = false;
+
+ return false;
+ });
}
getTargetData(target) {
@@ -332,12 +319,12 @@ export default class CreateMergeRequestDropdown {
xhr = this.createBranch();
}
- xhr.fail(() => {
+ xhr.catch(() => {
this.isCreatingMergeRequest = false;
this.isCreatingBranch = false;
- });
- xhr.always(() => this.enable());
+ this.enable();
+ });
this.disable();
}
--
cgit v1.2.1
From e63882e8b9074140b89a4a5f7dc39adf8a0b0324 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 10:34:22 +0000
Subject: Converted dropzone_input to axios
---
app/assets/javascripts/dropzone_input.js | 33 +++++++++++++++-----------------
1 file changed, 15 insertions(+), 18 deletions(-)
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 550dbdda922..ba89e5726fa 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -2,6 +2,7 @@ import Dropzone from 'dropzone';
import _ from 'underscore';
import './preview_markdown';
import csrf from './lib/utils/csrf';
+import axios from './lib/utils/axios_utils';
Dropzone.autoDiscover = false;
@@ -235,25 +236,21 @@ export default function dropzoneInput(form) {
uploadFile = (item, filename) => {
const formData = new FormData();
formData.append('file', item, filename);
- return $.ajax({
- url: uploadsPath,
- type: 'POST',
- data: formData,
- dataType: 'json',
- processData: false,
- contentType: false,
- headers: csrf.headers,
- beforeSend: () => {
- showSpinner();
- return closeAlertMessage();
- },
- success: (e, text, response) => {
- const md = response.responseJSON.link.markdown;
+
+ showSpinner();
+ closeAlertMessage();
+
+ axios.post(uploadsPath, formData)
+ .then(({ data }) => {
+ const md = data.link.markdown;
+
insertToTextArea(filename, md);
- },
- error: response => showError(response.responseJSON.message),
- complete: () => closeSpinner(),
- });
+ closeSpinner();
+ })
+ .catch((e) => {
+ showError(e.response.data.message);
+ closeSpinner();
+ });
};
updateAttachingMessage = (files, messageContainer) => {
--
cgit v1.2.1
From 3bb085654e588e894abca95510d00d89f59b1850 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 10:41:44 +0000
Subject: Converted due_date_select to axios
---
app/assets/javascripts/due_date_select.js | 48 ++++++++++++++-----------------
1 file changed, 21 insertions(+), 27 deletions(-)
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index ada985913bb..bd4c58b7cb1 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -1,6 +1,7 @@
/* global dateFormat */
import Pikaday from 'pikaday';
+import axios from './lib/utils/axios_utils';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
class DueDateSelect {
@@ -125,37 +126,30 @@ class DueDateSelect {
}
submitSelectedDate(isDropdown) {
- return $.ajax({
- type: 'PUT',
- url: this.issueUpdateURL,
- data: this.datePayload,
- dataType: 'json',
- beforeSend: () => {
- const selectedDateValue = this.datePayload[this.abilityName].due_date;
- const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
+ const selectedDateValue = this.datePayload[this.abilityName].due_date;
+ const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value';
- this.$loading.removeClass('hidden').fadeIn();
+ this.$loading.removeClass('hidden').fadeIn();
- if (isDropdown) {
- this.$dropdown.trigger('loading.gl.dropdown');
- this.$selectbox.hide();
- }
+ if (isDropdown) {
+ this.$dropdown.trigger('loading.gl.dropdown');
+ this.$selectbox.hide();
+ }
- this.$value.css('display', '');
- this.$valueContent.html(`${this.displayedDate} `);
- this.$sidebarValue.html(this.displayedDate);
+ this.$value.css('display', '');
+ this.$valueContent.html(`${this.displayedDate} `);
+ this.$sidebarValue.html(this.displayedDate);
- return selectedDateValue.length ?
- $('.js-remove-due-date-holder').removeClass('hidden') :
- $('.js-remove-due-date-holder').addClass('hidden');
- },
- }).done(() => {
- if (isDropdown) {
- this.$dropdown.trigger('loaded.gl.dropdown');
- this.$dropdown.dropdown('toggle');
- }
- return this.$loading.fadeOut();
- });
+ $('.js-remove-due-date-holder').toggleClass('hidden', selectedDateValue.length);
+
+ return axios.put(this.issueUpdateURL, this.datePayload)
+ .then(() => {
+ if (isDropdown) {
+ this.$dropdown.trigger('loaded.gl.dropdown');
+ this.$dropdown.dropdown('toggle');
+ }
+ return this.$loading.fadeOut();
+ });
}
}
--
cgit v1.2.1
From b3c9d965591c6d1ebfc821c6a7f121fa0ad0a24b Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 10:49:05 +0000
Subject: Converted filterable_list to axios
---
app/assets/javascripts/filterable_list.js | 29 +++++++++-------------
.../javascripts/groups/groups_filterable_list.js | 16 ++++++------
2 files changed, 20 insertions(+), 25 deletions(-)
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index 9e91f72b2ea..b84325ac5ce 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import axios from './lib/utils/axios_utils';
/**
* Makes search request for content when user types a value in the search input.
@@ -54,32 +55,26 @@ export default class FilterableList {
this.listFilterElement.removeEventListener('input', this.debounceFilter);
}
- filterResults(queryData) {
+ filterResults(params) {
if (this.isBusy) {
return false;
}
$(this.listHolderElement).fadeTo(250, 0.5);
- return $.ajax({
- url: this.getFilterEndpoint(),
- data: queryData,
- type: 'GET',
- dataType: 'json',
- context: this,
- complete: this.onFilterComplete,
- beforeSend: () => {
- this.isBusy = true;
- },
- success: (response, textStatus, xhr) => {
- this.onFilterSuccess(response, xhr, queryData);
- },
- });
+ this.isBusy = true;
+
+ return axios.get(this.getFilterEndpoint(), {
+ params,
+ }).then((res) => {
+ this.onFilterSuccess(res, params);
+ this.onFilterComplete();
+ }).catch(() => this.onFilterComplete());
}
- onFilterSuccess(response, xhr, queryData) {
+ onFilterSuccess(response, queryData) {
if (response.html) {
- this.listHolderElement.innerHTML = response.html;
+ this.listHolderElement.innerHTML = response.data.html;
}
// Change url so if user reload a page - search results are saved
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index 2db233b09da..3f65853508e 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -94,23 +94,23 @@ export default class GroupFilterableList extends FilterableList {
this.form.querySelector(`[name="${this.filterInputField}"]`).value = '';
}
- onFilterSuccess(data, xhr, queryData) {
+ onFilterSuccess(res, queryData) {
const currentPath = this.getPagePath(queryData);
const paginationData = {
- 'X-Per-Page': xhr.getResponseHeader('X-Per-Page'),
- 'X-Page': xhr.getResponseHeader('X-Page'),
- 'X-Total': xhr.getResponseHeader('X-Total'),
- 'X-Total-Pages': xhr.getResponseHeader('X-Total-Pages'),
- 'X-Next-Page': xhr.getResponseHeader('X-Next-Page'),
- 'X-Prev-Page': xhr.getResponseHeader('X-Prev-Page'),
+ 'X-Per-Page': res.headers['x-per-page'],
+ 'X-Page': res.headers['x-page'],
+ 'X-Total': res.headers['x-total'],
+ 'X-Total-Pages': res.headers['x-total-pages'],
+ 'X-Next-Page': res.headers['x-next-page'],
+ 'X-Prev-Page': res.headers['x-prev-page'],
};
window.history.replaceState({
page: currentPath,
}, document.title, currentPath);
- eventHub.$emit('updateGroups', data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
+ eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
eventHub.$emit('updatePagination', paginationData);
}
}
--
cgit v1.2.1
From c49fecdea5c3e638986be6f683f169bcaa2f973b Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 11:34:06 +0000
Subject: fixed dashboard projects not being filterable
---
app/assets/javascripts/filterable_list.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index b84325ac5ce..a10f027de53 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -73,7 +73,7 @@ export default class FilterableList {
}
onFilterSuccess(response, queryData) {
- if (response.html) {
+ if (response.data.html) {
this.listHolderElement.innerHTML = response.data.html;
}
--
cgit v1.2.1
From cf4fb53f560721ff6c5713886132d6678a62673d Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 15:04:44 +0000
Subject: normalize headers correctly i18n flash message
---
app/assets/javascripts/compare.js | 2 +-
app/assets/javascripts/compare_autocomplete.js | 3 ++-
app/assets/javascripts/groups/groups_filterable_list.js | 13 ++-----------
3 files changed, 5 insertions(+), 13 deletions(-)
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index 4c04fb40f31..e2a008e8904 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -77,7 +77,7 @@ export default class Compare {
}).then(({ data }) => {
loading.hide();
$target.html(data);
- var className = '.' + $target[0].className.replace(' ', '.');
+ const className = '.' + $target[0].className.replace(' ', '.');
localTimeAgo($('.js-timeago', className));
});
}
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 0787400e862..59899e97be1 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,4 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
+import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
@@ -19,7 +20,7 @@ export default function initCompareAutocomplete() {
},
}).then(({ data }) => {
callback(data);
- }).catch(() => flash('Error fetching refs'));
+ }).catch(() => flash(__('Error fetching refs')));
},
selectable: true,
filterable: true,
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index 3f65853508e..31d56d15c23 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -1,6 +1,6 @@
import FilterableList from '~/filterable_list';
import eventHub from './event_hub';
-import { getParameterByName } from '../lib/utils/common_utils';
+import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
export default class GroupFilterableList extends FilterableList {
constructor({ form, filter, holder, filterEndpoint, pagePath, dropdownSel, filterInputField }) {
@@ -97,20 +97,11 @@ export default class GroupFilterableList extends FilterableList {
onFilterSuccess(res, queryData) {
const currentPath = this.getPagePath(queryData);
- const paginationData = {
- 'X-Per-Page': res.headers['x-per-page'],
- 'X-Page': res.headers['x-page'],
- 'X-Total': res.headers['x-total'],
- 'X-Total-Pages': res.headers['x-total-pages'],
- 'X-Next-Page': res.headers['x-next-page'],
- 'X-Prev-Page': res.headers['x-prev-page'],
- };
-
window.history.replaceState({
page: currentPath,
}, document.title, currentPath);
eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
- eventHub.$emit('updatePagination', paginationData);
+ eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
}
}
--
cgit v1.2.1
From 0f000716891c97828207febe7cc521f94c3c9351 Mon Sep 17 00:00:00 2001
From: Jacopo
Date: Mon, 22 Jan 2018 22:33:07 +0100
Subject: Disable MR check out button when source branch is deleted
After source branch deletion the Merge request `Check out branch` button is disabled.
---
.../components/mr_widget_header.js | 1 +
...-checkout-button-when-source-branch-deleted.yml | 5 +++
.../components/mr_widget_header_spec.js | 38 +++++++++++++++-------
3 files changed, 33 insertions(+), 11 deletions(-)
create mode 100644 changelogs/unreleased/42255-disable-mr-checkout-button-when-source-branch-deleted.yml
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
index 85bfd03a3cf..de6e5149a87 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
@@ -76,6 +76,7 @@ export default {
Check out branch
diff --git a/changelogs/unreleased/42255-disable-mr-checkout-button-when-source-branch-deleted.yml b/changelogs/unreleased/42255-disable-mr-checkout-button-when-source-branch-deleted.yml
new file mode 100644
index 00000000000..bd7e0d3a1b0
--- /dev/null
+++ b/changelogs/unreleased/42255-disable-mr-checkout-button-when-source-branch-deleted.yml
@@ -0,0 +1,5 @@
+---
+title: Disable MR check out button when source branch is deleted
+merge_request: 16631
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 06f89fabf42..93bb83ca8bd 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -48,20 +48,23 @@ describe('MRWidgetHeader', () => {
describe('template', () => {
let vm;
let el;
+ let mr;
const sourceBranchPath = '/foo/bar/mr-widget-refactor';
- const mr = {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: `mr-widget-refactor `,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- };
beforeEach(() => {
+ mr = {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: `mr-widget-refactor `,
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ };
+
vm = createComponent(mr);
el = vm.$el;
});
@@ -82,6 +85,8 @@ describe('MRWidgetHeader', () => {
expect(el.textContent).toContain('Check out branch');
expect(el.querySelectorAll('.dropdown li a')[0].getAttribute('href')).toEqual(mr.emailPatchesPath);
expect(el.querySelectorAll('.dropdown li a')[1].getAttribute('href')).toEqual(mr.plainDiffPath);
+
+ expect(el.querySelector('a[href="#modal_merge_info"]').getAttribute('disabled')).toBeNull();
});
it('should not have right action links if the MR state is not open', (done) => {
@@ -101,5 +106,16 @@ describe('MRWidgetHeader', () => {
done();
});
});
+
+ it('should disable check out branch button if source branch has been removed', (done) => {
+ vm.mr.sourceBranchRemoved = true;
+
+ Vue.nextTick()
+ .then(() => {
+ expect(el.querySelector('a[href="#modal_merge_info"]').getAttribute('disabled')).toBe('disabled');
+ done();
+ })
+ .catch(done.fail);
+ });
});
});
--
cgit v1.2.1
From f0df45fb8a79056c81da659678be571329831cff Mon Sep 17 00:00:00 2001
From: Sean McGivern
Date: Mon, 29 Jan 2018 10:49:14 +0000
Subject: Fix MR revert check when no merged_at is present
---
app/models/merge_request.rb | 12 ++++++------
spec/models/merge_request_spec.rb | 30 ++++++++++++++++++++----------
2 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 4accb08eaf9..f6d4843abc3 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -989,13 +989,13 @@ class MergeRequest < ActiveRecord::Base
merged_at = metrics&.merged_at
notes_association = notes_with_associations
- # It is not guaranteed that Note#created_at will be strictly later than
- # MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
- # comparison, as will a HA environment if clocks are not *precisely*
- # synchronized. Add a minute's leeway to compensate for both possibilities
- cutoff = merged_at - 1.minute
-
if merged_at
+ # It is not guaranteed that Note#created_at will be strictly later than
+ # MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
+ # comparison, as will a HA environment if clocks are not *precisely*
+ # synchronized. Add a minute's leeway to compensate for both possibilities
+ cutoff = merged_at - 1.minute
+
notes_association = notes_association.where('created_at >= ?', cutoff)
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 429b6615131..15f9c68e400 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1064,16 +1064,6 @@ describe MergeRequest do
end
describe '#can_be_reverted?' do
- context 'when there is no merged_at for the MR' do
- before do
- subject.metrics.update!(merged_at: nil)
- end
-
- it 'returns false' do
- expect(subject.can_be_reverted?(nil)).to be_falsey
- end
- end
-
context 'when there is no merge_commit for the MR' do
before do
subject.metrics.update!(merged_at: Time.now.utc)
@@ -1097,6 +1087,16 @@ describe MergeRequest do
end
end
+ context 'when there is no merged_at for the MR' do
+ before do
+ subject.metrics.update!(merged_at: nil)
+ end
+
+ it 'returns true' do
+ expect(subject.can_be_reverted?(nil)).to be_truthy
+ end
+ end
+
context 'when there is a revert commit' do
let(:current_user) { subject.author }
let(:branch) { subject.target_branch }
@@ -1127,6 +1127,16 @@ describe MergeRequest do
end
end
+ context 'when there is no merged_at for the MR' do
+ before do
+ subject.metrics.update!(merged_at: nil)
+ end
+
+ it 'returns false' do
+ expect(subject.can_be_reverted?(current_user)).to be_falsey
+ end
+ end
+
context 'when the revert commit is mentioned in a note just before the MR was merged' do
before do
subject.notes.last.update!(created_at: subject.metrics.merged_at - 30.seconds)
--
cgit v1.2.1
From ec918997eef4bdc4e2df1fe961083350eddf2492 Mon Sep 17 00:00:00 2001
From: George Tsiolis
Date: Mon, 29 Jan 2018 00:43:47 +0200
Subject: Improve issue mr and branch dropdown button
---
app/assets/stylesheets/pages/note_form.scss | 1 -
app/views/projects/issues/_new_branch.html.haml | 4 ++--
changelogs/unreleased/fix-improve-issue-note-dropdown.yml | 5 +++++
3 files changed, 7 insertions(+), 3 deletions(-)
create mode 100644 changelogs/unreleased/fix-improve-issue-note-dropdown.yml
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index bf8eb4c1f06..4a528bc2bb1 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -410,7 +410,6 @@
width: 298px;
}
-
@media (max-width: $screen-xs-max) {
display: flex;
width: 100%;
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 331d62cf247..37b00a14fc6 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -15,10 +15,10 @@
%span.text
Checking branch availability…
.btn-group.available.hide
- %button.btn.js-create-merge-request.btn-default{ type: 'button', data: { action: data_action } }
+ %button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } }
= value
- %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-default.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' } } }
+ %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' } } }
= icon('caret-down')
%ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors{ data: { dropdown: true } }
diff --git a/changelogs/unreleased/fix-improve-issue-note-dropdown.yml b/changelogs/unreleased/fix-improve-issue-note-dropdown.yml
new file mode 100644
index 00000000000..aaf4811c64a
--- /dev/null
+++ b/changelogs/unreleased/fix-improve-issue-note-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Improve issue note dropdown and mr button
+merge_request: 16758
+author: George Tsiolis
+type: changed
--
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"
/>
-
{{ revertLabel }}
-
+
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 3c0aa44fa9125ce7c360f64994e483cc9e7cb951 Mon Sep 17 00:00:00 2001
From: Clement Ho
Date: Mon, 29 Jan 2018 12:32:22 +0000
Subject: Replace $.post in awards handler with axios
---
app/assets/javascripts/awards_handler.js | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 622764107ad..d9341837149 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,8 +1,10 @@
/* eslint-disable class-methods-use-this */
import _ from 'underscore';
import Cookies from 'js-cookie';
+import { s__ } from './locale';
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
-import Flash from './flash';
+import flash from './flash';
+import axios from './lib/utils/axios_utils';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@@ -441,13 +443,15 @@ class AwardsHandler {
if (this.isUserAuthored($emojiButton)) {
this.userAuthored($emojiButton);
} else {
- $.post(awardUrl, {
+ axios.post(awardUrl, {
name: emoji,
- }, (data) => {
+ })
+ .then(({ data }) => {
if (data.ok) {
callback();
}
- }).fail(() => new Flash('Something went wrong on our end.'));
+ })
+ .catch(() => flash(s__('Something went wrong on our end.')));
}
}
--
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 ef44fef15f6cacc1f3ecb6ecd10a919004b57dae Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Fri, 12 Jan 2018 17:55:53 +0100
Subject: Initiaal implementation of metrics concern
---
lib/gitlab/metrics/concern.rb | 84 +++++++++++++++++++++++++++
lib/gitlab/metrics/method_call.rb | 37 ++----------
lib/gitlab/metrics/subscribers/action_view.rb | 19 ++----
3 files changed, 96 insertions(+), 44 deletions(-)
create mode 100644 lib/gitlab/metrics/concern.rb
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
new file mode 100644
index 00000000000..ccc55cf5b76
--- /dev/null
+++ b/lib/gitlab/metrics/concern.rb
@@ -0,0 +1,84 @@
+module Gitlab
+ module Metrics
+ module Concern
+ extend ActiveSupport::Concern
+ class_methods do
+ private
+
+ def metrics_provider(type, name, docstring, options = {})
+ @@_metrics_provider_mutex ||= Mutex.new
+
+ if instance_methods(false).include?(name)
+ raise ArgumentError, "metrics class method #{name} already exists"
+ end
+ options[:base_labels] ||= {}
+
+ args = [name.inspect, %{"#{docstring}"}, options[:base_labels].inspect]
+
+ case type
+ when :gauge
+ options[:multiprocess_mode] ||= :all
+ args << options[:multiprocess_mode].inspect
+ when :histogram
+ options[:buckets] ||= ::Prometheus::Client::Histogram::DEFAULT_BUCKETS
+ args << options[:buckets].inspect
+ end
+
+ metric_fetching_code = %{Gitlab::Metrics::Prometheus.#{type}(#{args.join(', ')})}
+
+ # optionally wrap in feature
+ metric_fetching_code = if options[:with_feature].is_a?(Symbol)
+ <<-FETCH.strip_heredoc
+ if Feature.get(#{options[:with_feature].inspect}).enabled?
+ #{metric_fetching_code}
+ else
+ Gitlab::Metrics::NullMetric.new
+ end
+ FETCH
+ end
+
+ method_code, line = <<-METRIC, __LINE__ + 1
+ def #{name}
+ @@_metric_provider_cached_#{name} if @@_metric_provider_cached_#{name}
+
+ @@_metrics_provider_mutex.synchronize do
+ @_metric_provider_cached_#{name} ||= #{metric_fetching_code}
+ end
+ end
+ METRIC
+
+ class_eval(method_code, __FILE__, line)
+ module_eval(method_code, __FILE__, line)
+ end
+
+ # Declare a Counter
+ # @param [Symbol] name
+ # @param [String] docstring
+ # @param [Hash] opts
+ def counter(name, docstring, opts = {})
+ metrics_provider(:counter, name, docstring, options)
+ end
+
+ # Declare a Gauge
+ # @param [Symbol] name
+ # @param [String] docstring
+ # @param [Hash] opts
+ def gauge(name, docstring, opts = {})
+ metrics_provider(:counter, name, docstring, opts)
+ end
+
+ # Declare a Histograam
+ # @param [Symbol] name
+ # @param [String] docstring
+ # @param [Hash] opts
+ def histogram(name, docstring, opts = {})
+ metrics_provider(:histogram, name, docstring, opts)
+ end
+
+ def summary(*args)
+ raise NotImplementedError, "summary metrics are not currently supported"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index c2f9db56824..2671b612abd 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -4,27 +4,13 @@ module Gitlab
module Metrics
# Class for tracking timing information about method calls
class MethodCall
- @@measurement_enabled_cache = Concurrent::AtomicBoolean.new(false)
- @@measurement_enabled_cache_expires_at = Concurrent::AtomicReference.new(Time.now.to_i)
- MUTEX = Mutex.new
+ include Gitlab::Metrics::Concern
BASE_LABELS = { module: nil, method: nil }.freeze
attr_reader :real_time, :cpu_time, :call_count, :labels
- def self.call_duration_histogram
- return @call_duration_histogram if @call_duration_histogram
-
- MUTEX.synchronize do
- @call_duration_histogram ||= Gitlab::Metrics.histogram(
- :gitlab_method_call_duration_seconds,
- 'Method calls real duration',
- Transaction::BASE_LABELS.merge(BASE_LABELS),
- [0.01, 0.05, 0.1, 0.5, 1])
- end
- end
-
- def self.measurement_enabled_cache_expires_at
- @@measurement_enabled_cache_expires_at
- end
+ histogram :gitlab_method_call_duration_seconds, 'Method calls real duration',
+ base_labels: Transaction::BASE_LABELS.merge(BASE_LABELS),
+ buckets: [0.01, 0.05, 0.1, 0.5, 1]
# name - The full name of the method (including namespace) such as
# `User#sign_in`.
@@ -53,8 +39,8 @@ module Gitlab
@cpu_time += cpu_time
@call_count += 1
- if call_measurement_enabled? && above_threshold?
- self.class.call_duration_histogram.observe(@transaction.labels.merge(labels), real_time)
+ if above_threshold?
+ gitlab_method_call_duration_seconds.observe(@transaction.labels.merge(labels), real_time)
end
retval
@@ -78,17 +64,6 @@ module Gitlab
def above_threshold?
real_time.in_milliseconds >= Metrics.method_call_threshold
end
-
- def call_measurement_enabled?
- expires_at = @@measurement_enabled_cache_expires_at.value
- if expires_at < Time.now.to_i
- if @@measurement_enabled_cache_expires_at.compare_and_set(expires_at, 1.minute.from_now.to_i)
- @@measurement_enabled_cache.value = Feature.get(:prometheus_metrics_method_instrumentation).enabled?
- end
- end
-
- @@measurement_enabled_cache.value
- end
end
end
end
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index 3da474fc1ec..5c390a077c2 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -3,6 +3,11 @@ module Gitlab
module Subscribers
# Class for tracking the rendering timings of views.
class ActionView < ActiveSupport::Subscriber
+ include Gitlab::Metrics::Concern
+ histogram :gitlab_view_rendering_duration_seconds, 'View rendering time',
+ base_labels: Transaction::BASE_LABELS.merge({ path: nil }),
+ buckets: [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
+
attach_to :action_view
SERIES = 'views'.freeze
@@ -15,23 +20,11 @@ module Gitlab
private
- def metric_view_rendering_duration_seconds
- @metric_view_rendering_duration_seconds ||= Gitlab::Metrics.histogram(
- :gitlab_view_rendering_duration_seconds,
- 'View rendering time',
- Transaction::BASE_LABELS.merge({ path: nil }),
- [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
- )
- end
-
def track(event)
values = values_for(event)
tags = tags_for(event)
- metric_view_rendering_duration_seconds.observe(
- current_transaction.labels.merge(tags),
- event.duration
- )
+ gitlab_view_rendering_duration_seconds.observe(current_transaction.labels.merge(tags), event.duration)
current_transaction.increment(:view_duration, event.duration)
current_transaction.add_metric(SERIES, values, tags)
--
cgit v1.2.1
From e5d6141541712b6bf2532aa654a5ad499cc26ea1 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Sat, 13 Jan 2018 00:20:27 +0100
Subject: Actually make the new methods work
---
lib/gitlab/metrics/concern.rb | 47 +++++++++++++++++++++------------------
lib/gitlab/metrics/method_call.rb | 3 ++-
2 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index ccc55cf5b76..093bd960d02 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -9,7 +9,7 @@ module Gitlab
@@_metrics_provider_mutex ||= Mutex.new
if instance_methods(false).include?(name)
- raise ArgumentError, "metrics class method #{name} already exists"
+ raise ArgumentError, "metrics method #{name} already exists"
end
options[:base_labels] ||= {}
@@ -24,55 +24,58 @@ module Gitlab
args << options[:buckets].inspect
end
- metric_fetching_code = %{Gitlab::Metrics::Prometheus.#{type}(#{args.join(', ')})}
+ metric_fetching_code = %{Gitlab::Metrics.#{type}(#{args.join(', ')})}
# optionally wrap in feature
- metric_fetching_code = if options[:with_feature].is_a?(Symbol)
- <<-FETCH.strip_heredoc
- if Feature.get(#{options[:with_feature].inspect}).enabled?
- #{metric_fetching_code}
- else
- Gitlab::Metrics::NullMetric.new
- end
- FETCH
- end
+ if options[:with_feature].is_a?(Symbol)
+ metric_fetching_code = <<-FETCH.strip_heredoc
+ if Feature.get(#{options[:with_feature].inspect}).enabled?
+ #{metric_fetching_code}
+ else
+ Gitlab::Metrics::NullMetric.new
+ end
+ FETCH
+ end
method_code, line = <<-METRIC, __LINE__ + 1
+ @@_metric_provider_cached_#{name} = nil
def #{name}
- @@_metric_provider_cached_#{name} if @@_metric_provider_cached_#{name}
+ return @@_metric_provider_cached_#{name} if @@_metric_provider_cached_#{name}
@@_metrics_provider_mutex.synchronize do
- @_metric_provider_cached_#{name} ||= #{metric_fetching_code}
+ puts "Initiaalized"
+ @@_metric_provider_cached_#{name} ||= #{metric_fetching_code}
end
end
METRIC
+ puts method_code
- class_eval(method_code, __FILE__, line)
+ instance_eval(method_code, __FILE__, line)
module_eval(method_code, __FILE__, line)
end
# Declare a Counter
# @param [Symbol] name
# @param [String] docstring
- # @param [Hash] opts
- def counter(name, docstring, opts = {})
+ # @param [Hash] options
+ def counter(name, docstring, options = {})
metrics_provider(:counter, name, docstring, options)
end
# Declare a Gauge
# @param [Symbol] name
# @param [String] docstring
- # @param [Hash] opts
- def gauge(name, docstring, opts = {})
- metrics_provider(:counter, name, docstring, opts)
+ # @param [Hash] options
+ def gauge(name, docstring, options = {})
+ metrics_provider(:counter, name, docstring, options)
end
# Declare a Histograam
# @param [Symbol] name
# @param [String] docstring
- # @param [Hash] opts
- def histogram(name, docstring, opts = {})
- metrics_provider(:histogram, name, docstring, opts)
+ # @param [Hash] options
+ def histogram(name, docstring, options = {})
+ metrics_provider(:histogram, name, docstring, options)
end
def summary(*args)
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index 2671b612abd..d4698d83a05 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -10,7 +10,8 @@ module Gitlab
histogram :gitlab_method_call_duration_seconds, 'Method calls real duration',
base_labels: Transaction::BASE_LABELS.merge(BASE_LABELS),
- buckets: [0.01, 0.05, 0.1, 0.5, 1]
+ buckets: [0.01, 0.05, 0.1, 0.5, 1],
+ with_feature: :prometheus_metrics_method_instrumentation
# name - The full name of the method (including namespace) such as
# `User#sign_in`.
--
cgit v1.2.1
From d97548daa82ab02884a6eaffe7d9e32d754b3a08 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 15 Jan 2018 22:06:40 +0100
Subject: cleanup method call
---
lib/gitlab/metrics/concern.rb | 6 ++--
lib/gitlab/metrics/method_call.rb | 2 +-
spec/lib/gitlab/metrics/method_call_spec.rb | 44 ++++++++---------------------
3 files changed, 17 insertions(+), 35 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 093bd960d02..940337deb74 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -43,12 +43,14 @@ module Gitlab
return @@_metric_provider_cached_#{name} if @@_metric_provider_cached_#{name}
@@_metrics_provider_mutex.synchronize do
- puts "Initiaalized"
@@_metric_provider_cached_#{name} ||= #{metric_fetching_code}
end
end
+
+ def reload_#{name}!
+ @@_metric_provider_cached_#{name} = nil
+ end
METRIC
- puts method_code
instance_eval(method_code, __FILE__, line)
module_eval(method_code, __FILE__, line)
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index d4698d83a05..5cd5c388fb6 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -41,7 +41,7 @@ module Gitlab
@call_count += 1
if above_threshold?
- gitlab_method_call_duration_seconds.observe(@transaction.labels.merge(labels), real_time)
+ self.class.gitlab_method_call_duration_seconds.observe(@transaction.labels.merge(labels), real_time)
end
retval
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 41a9d1d9c90..fcdd456613c 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -5,6 +5,10 @@ describe Gitlab::Metrics::MethodCall do
let(:method_call) { described_class.new('Foo#bar', :Foo, '#bar', transaction) }
describe '#measure' do
+ before do
+ described_class.reload_gitlab_method_call_duration_seconds!
+ end
+
it 'measures the performance of the supplied block' do
method_call.measure { 'foo' }
@@ -20,8 +24,6 @@ describe Gitlab::Metrics::MethodCall do
context 'prometheus instrumentation is enabled' do
before do
- allow(Feature.get(:prometheus_metrics_method_instrumentation)).to receive(:enabled?).and_call_original
- described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1
Feature.get(:prometheus_metrics_method_instrumentation).enable
end
@@ -31,30 +33,12 @@ describe Gitlab::Metrics::MethodCall do
end
end
- it 'caches subsequent invocations of feature check' do
- 10.times do
- method_call.measure { 'foo' }
- end
-
- expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).once
- end
-
- it 'expires feature check cache after 1 minute' do
- method_call.measure { 'foo' }
-
- Timecop.travel(1.minute.from_now) do
- method_call.measure { 'foo' }
- end
-
- Timecop.travel(1.minute.from_now + 1.second) do
- method_call.measure { 'foo' }
- end
-
- expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).twice
+ it 'metric is not a NullMetric' do
+ expect(described_class).not_to be_instance_of(Gitlab::Metrics::NullMetric)
end
it 'observes the performance of the supplied block' do
- expect(described_class.call_duration_histogram)
+ expect(described_class.gitlab_method_call_duration_seconds)
.to receive(:observe)
.with({ module: :Foo, method: '#bar' }, be_a_kind_of(Numeric))
@@ -64,14 +48,12 @@ describe Gitlab::Metrics::MethodCall do
context 'prometheus instrumentation is disabled' do
before do
- described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1
-
Feature.get(:prometheus_metrics_method_instrumentation).disable
end
- it 'does not observe the performance' do
- expect(described_class.call_duration_histogram)
- .not_to receive(:observe)
+ it 'observes using NullMetric' do
+ expect(described_class.gitlab_method_call_duration_seconds).to be_instance_of(Gitlab::Metrics::NullMetric)
+ expect(described_class.gitlab_method_call_duration_seconds).to receive(:observe)
method_call.measure { 'foo' }
end
@@ -81,12 +63,10 @@ describe Gitlab::Metrics::MethodCall do
context 'when measurement is below threshold' do
before do
allow(method_call).to receive(:above_threshold?).and_return(false)
-
- Feature.get(:prometheus_metrics_method_instrumentation).enable
end
it 'does not observe the performance' do
- expect(described_class.call_duration_histogram)
+ expect(described_class.gitlab_method_call_duration_seconds)
.not_to receive(:observe)
method_call.measure { 'foo' }
@@ -96,7 +76,7 @@ describe Gitlab::Metrics::MethodCall do
describe '#to_metric' do
it 'returns a Metric instance' do
- expect(method_call).to receive(:real_time).and_return(4.0001)
+ expect(method_call).to receive(:real_time).and_return(4.0001).twice
expect(method_call).to receive(:cpu_time).and_return(3.0001)
method_call.measure { 'foo' }
--
cgit v1.2.1
From e56b3d180dc9b251f00f0611435a6f25d708d2b6 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Tue, 16 Jan 2018 08:50:42 +0100
Subject: Refactor transaction metrics
---
lib/gitlab/metrics/transaction.rb | 81 +++++++++++----------------------------
1 file changed, 22 insertions(+), 59 deletions(-)
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index e7975c023a9..8d979a1ef72 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -54,8 +54,8 @@ module Gitlab
@memory_after = System.memory_usage
@finished_at = System.monotonic_time
- self.class.metric_transaction_duration_seconds.observe(labels, duration)
- self.class.metric_transaction_allocated_memory_bytes.observe(labels, allocated_memory * 1024.0)
+ self.class.gitlab_transaction_duration_seconds.observe(labels, duration)
+ self.class.gitlab_transaction_allocated_memory_bytes.observe(labels, allocated_memory * 1024.0)
Thread.current[THREAD_KEY] = nil
end
@@ -72,7 +72,7 @@ module Gitlab
# event_name - The name of the event (e.g. "git_push").
# tags - A set of tags to attach to the event.
def add_event(event_name, tags = {})
- self.class.metric_event_counter(event_name, tags).increment(tags.merge(labels))
+ self.class.transaction_metric(event_name, :counter, prefix: 'event_', tags: tags).increment(tags.merge(labels))
@metrics << Metric.new(EVENT_SERIES, { count: 1 }, tags.merge(event: event_name), :event)
end
@@ -86,12 +86,12 @@ module Gitlab
end
def increment(name, value, use_prometheus = true)
- self.class.metric_transaction_counter(name).increment(labels, value) if use_prometheus
+ self.class.transaction_metric(name, :counter).increment(labels, value) if use_prometheus
@values[name] += value
end
def set(name, value, use_prometheus = true)
- self.class.metric_transaction_gauge(name).set(labels, value) if use_prometheus
+ self.class.transaction_metric(name, :gauge).set(labels, value) if use_prometheus
@values[name] = value
end
@@ -136,64 +136,27 @@ module Gitlab
"#{labels[:controller]}##{labels[:action]}" if labels && !labels.empty?
end
- def self.metric_transaction_duration_seconds
- return @metric_transaction_duration_seconds if @metric_transaction_duration_seconds
+ histogram :gitlab_transaction_duration_seconds, 'Transaction duration',
+ base_labels: BASE_LABELS,
+ buckets: [0.001, 0.01, 0.1, 0.5, 10.0],
+ with_feature: :prometheus_metrics_method_instrumentation
- METRICS_MUTEX.synchronize do
- @metric_transaction_duration_seconds ||= Gitlab::Metrics.histogram(
- :gitlab_transaction_duration_seconds,
- 'Transaction duration',
- BASE_LABELS,
- [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
- )
- end
- end
-
- def self.metric_transaction_allocated_memory_bytes
- return @metric_transaction_allocated_memory_bytes if @metric_transaction_allocated_memory_bytes
-
- METRICS_MUTEX.synchronize do
- @metric_transaction_allocated_memory_bytes ||= Gitlab::Metrics.histogram(
- :gitlab_transaction_allocated_memory_bytes,
- 'Transaction allocated memory bytes',
- BASE_LABELS,
- [1000, 10000, 20000, 500000, 1000000, 2000000, 5000000, 10000000, 20000000, 100000000]
- )
- end
- end
-
- def self.metric_event_counter(event_name, tags)
- return @metric_event_counters[event_name] if @metric_event_counters&.has_key?(event_name)
-
- METRICS_MUTEX.synchronize do
- @metric_event_counters ||= {}
- @metric_event_counters[event_name] ||= Gitlab::Metrics.counter(
- "gitlab_transaction_event_#{event_name}_total".to_sym,
- "Transaction event #{event_name} counter",
- tags.merge(BASE_LABELS)
- )
- end
- end
-
- def self.metric_transaction_counter(name)
- return @metric_transaction_counters[name] if @metric_transaction_counters&.has_key?(name)
-
- METRICS_MUTEX.synchronize do
- @metric_transaction_counters ||= {}
- @metric_transaction_counters[name] ||= Gitlab::Metrics.counter(
- "gitlab_transaction_#{name}_total".to_sym, "Transaction #{name} counter", BASE_LABELS
- )
- end
- end
+ histogram :gitlab_transaction_allocated_memory_bytes, 'Transaction allocated memory bytes',
+ base_labels: BASE_LABELS,
+ buckets: [100, 1000, 10000, 100000, 1000000, 10000000]
- def self.metric_transaction_gauge(name)
- return @metric_transaction_gauges[name] if @metric_transaction_gauges&.has_key?(name)
+ def self.transaction_metric(name, type, prefix: nil, tags: {})
+ return @transaction_metric[name] if @transaction_metric[name]&.has_key?(name)
METRICS_MUTEX.synchronize do
- @metric_transaction_gauges ||= {}
- @metric_transaction_gauges[name] ||= Gitlab::Metrics.gauge(
- "gitlab_transaction_#{name}".to_sym, "Transaction gauge #{name}", BASE_LABELS, :livesum
- )
+ @transaction_metric ||= {}
+ @transaction_metric[name] ||= if type == :counter
+ Gitlab::Metrics.counter("gitlab_transaction_#{prefix}#{name}_total".to_sym,
+ "Transaction #{prefix}#{name} counter", tags.merge(BASE_LABELS))
+ else
+ Gitlab::Metrics.gauge("gitlab_transaction_#{name}".to_sym,
+ "Transaction gauge #{name} ", tags.merge(BASE_LABELS), :livesum)
+ end
end
end
end
--
cgit v1.2.1
From 53f818fdac35d1c0810faa81a8dde20bbf8a3697 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Tue, 16 Jan 2018 14:06:56 +0100
Subject: Refactor method concern to use hashes
---
lib/gitlab/metrics/concern.rb | 138 +++++++++++++++++----------
lib/gitlab/metrics/concern/metric_options.rb | 48 ++++++++++
2 files changed, 135 insertions(+), 51 deletions(-)
create mode 100644 lib/gitlab/metrics/concern/metric_options.rb
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 940337deb74..a9c45380a5d 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -2,86 +2,122 @@ module Gitlab
module Metrics
module Concern
extend ActiveSupport::Concern
+
+ included do
+ @@_metrics_provider_mutex ||= Mutex.new
+ @@_metrics_provider_cache ||= {}
+ end
+
class_methods do
private
- def metrics_provider(type, name, docstring, options = {})
- @@_metrics_provider_mutex ||= Mutex.new
-
+ def define_metric(type, name, opts = {}, &block)
if instance_methods(false).include?(name)
raise ArgumentError, "metrics method #{name} already exists"
end
- options[:base_labels] ||= {}
- args = [name.inspect, %{"#{docstring}"}, options[:base_labels].inspect]
+ define_method(name) do
+ # avoid unnecessary method call to speed up metric access
+ return @@_metrics_provider_cache[name] if @@_metrics_provider_cache.has_key?(name)
+
+ fetch_metric(type, name, opts, &block)
+ end
+ end
+
+ def fetch_metric(type, name, opts = {}, &block)
+ # avoid synchronization to speed up metrics access
+ return @@_metrics_provider_cache[name] if @@_metrics_provider_cache.has_key?(name)
+
+ options = MetricOptions.new(opts)
+ options.evaluate(&block)
+
+ @@_metrics_provider_mutex.synchronize do
+ @@_metrics_provider_cache[name] ||= build_metric!(type, name, options)
+ end
+
+ @@_metrics_provider_cache[name]
+ end
+
+ def build_metric!(type, name, options)
+ unless options.with_feature.nil? || Feature.get(options.with_feature).enabled?
+ return NullMetric.new
+ end
case type
when :gauge
- options[:multiprocess_mode] ||= :all
- args << options[:multiprocess_mode].inspect
+ Gitlab::Metrics.gauge(name, options.docs, options.base_labels, options.multiprocess_mode)
+ when :counter
+ Gitlab::Metrics.counter(name, options.docs, options.base_labels)
when :histogram
options[:buckets] ||= ::Prometheus::Client::Histogram::DEFAULT_BUCKETS
- args << options[:buckets].inspect
+ Gitlab::Metrics.histogram(name, options.docs, options.base_labels, options.buckets)
+ when :summary
+ raise NotImplementedError, "summary metrics are not currently supported"
+ else
+ raise ArgumentError, "uknown metric type #{type}"
end
+ end
- metric_fetching_code = %{Gitlab::Metrics.#{type}(#{args.join(', ')})}
-
- # optionally wrap in feature
- if options[:with_feature].is_a?(Symbol)
- metric_fetching_code = <<-FETCH.strip_heredoc
- if Feature.get(#{options[:with_feature].inspect}).enabled?
- #{metric_fetching_code}
- else
- Gitlab::Metrics::NullMetric.new
- end
- FETCH
- end
+ counter :global do
+ docstring "Global counter"
+ multiprocess_mode :all
+ buckets [0, 1]
+ end
- method_code, line = <<-METRIC, __LINE__ + 1
- @@_metric_provider_cached_#{name} = nil
- def #{name}
- return @@_metric_provider_cached_#{name} if @@_metric_provider_cached_#{name}
+ # Fetch and/or initialize counter metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_counter(name, opts = {}, &block)
+ define_metric(:counter, name, opts, &block)
+ end
- @@_metrics_provider_mutex.synchronize do
- @@_metric_provider_cached_#{name} ||= #{metric_fetching_code}
- end
- end
+ # DFetch and/or initialize gauge metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_gauge(name, opts = {}, &block)
+ define_metric(:counter, name, opts, &block)
+ end
- def reload_#{name}!
- @@_metric_provider_cached_#{name} = nil
- end
- METRIC
+ # Fetch and/or initialize histogram metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_histogram(name, opts = {}, &block)
+ define_metric(:histogram, name, opts, &block)
+ end
- instance_eval(method_code, __FILE__, line)
- module_eval(method_code, __FILE__, line)
+ # Fetch and/or initialize summary metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_summary(name, opts = {}, &block)
+ define_metric(:summary, name, opts, &block)
end
- # Declare a Counter
+ # Define metric accessor method for a Counter
# @param [Symbol] name
- # @param [String] docstring
- # @param [Hash] options
- def counter(name, docstring, options = {})
- metrics_provider(:counter, name, docstring, options)
+ # @param [Hash] opts
+ def define_counter(name, opts = {}, &block)
+ define_metric(:counter, name, opts, &block)
end
- # Declare a Gauge
+ # Define metric accessor method for a Gauge
# @param [Symbol] name
- # @param [String] docstring
- # @param [Hash] options
- def gauge(name, docstring, options = {})
- metrics_provider(:counter, name, docstring, options)
+ # @param [Hash] opts
+ def define_gauge(name, opts = {}, &block)
+ define_metric(:counter, name, opts, &block)
end
- # Declare a Histograam
+ # Define metric accessor method for a Histogram
# @param [Symbol] name
- # @param [String] docstring
- # @param [Hash] options
- def histogram(name, docstring, options = {})
- metrics_provider(:histogram, name, docstring, options)
+ # @param [Hash] opts
+ def define_histogram(name, opts = {}, &block)
+ define_metric(:histogram, name, opts, &block)
end
- def summary(*args)
- raise NotImplementedError, "summary metrics are not currently supported"
+ # Define metric accessor method for a Summary
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def define_summary(name, opts = {}, &block)
+ define_metric(:summary, name, opts, &block)
end
end
end
diff --git a/lib/gitlab/metrics/concern/metric_options.rb b/lib/gitlab/metrics/concern/metric_options.rb
new file mode 100644
index 00000000000..502805c135a
--- /dev/null
+++ b/lib/gitlab/metrics/concern/metric_options.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module Metrics
+ module Concern
+ class MetricOptions
+ def initialize(options = {})
+ @multiprocess_mode = options[:multiprocess_mode] || :all
+ @buckets = options[:buckets] || ::Prometheus::Client::Histogram::DEFAULT_BUCKETS
+ @base_labels = options[:base_labels] || {}
+ end
+
+ def docs(docs = nil)
+ @docs = docs unless docs.nil?
+
+ @docs
+ end
+
+ def multiprocess_mode(mode)
+ @multiprocess_mode = mode unless @multiprocess_mode.nil?
+
+ @multiprocess_mode
+ end
+
+ def buckets(buckets)
+ @buckets = buckets unless @buckets.nil?
+
+ @buckets
+ end
+
+ def base_labels(base_labels)
+ @base_labels = base_labels unless @base_labels.nil?
+
+ @base_labels
+ end
+
+ def with_feature(name)
+ @feature_name = name unless @feature_name.nil?
+
+ @feature_name
+ end
+
+ def evaluate(&block)
+ instance_eval(&block) if block_given?
+ self
+ end
+ end
+ end
+ end
+end
--
cgit v1.2.1
From 66c1acba0bada535cedcdc4d8e62ff966b4d0374 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Tue, 16 Jan 2018 15:47:07 +0100
Subject: Fix code after refactoring
---
lib/gitlab/metrics/concern.rb | 19 ++++++--------
lib/gitlab/metrics/concern/metric_options.rb | 24 +++++++++---------
lib/gitlab/metrics/method_call.rb | 9 ++++---
lib/gitlab/metrics/transaction.rb | 37 ++++++++++++++--------------
spec/lib/gitlab/metrics/method_call_spec.rb | 2 +-
5 files changed, 45 insertions(+), 46 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index a9c45380a5d..e7c91df94bc 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -9,6 +9,10 @@ module Gitlab
end
class_methods do
+ def reload_metric!(name)
+ @@_metrics_provider_cache.delete(name)
+ end
+
private
def define_metric(type, name, opts = {}, &block)
@@ -16,7 +20,7 @@ module Gitlab
raise ArgumentError, "metrics method #{name} already exists"
end
- define_method(name) do
+ define_singleton_method(name) do
# avoid unnecessary method call to speed up metric access
return @@_metrics_provider_cache[name] if @@_metrics_provider_cache.has_key?(name)
@@ -45,12 +49,11 @@ module Gitlab
case type
when :gauge
- Gitlab::Metrics.gauge(name, options.docs, options.base_labels, options.multiprocess_mode)
+ Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
when :counter
- Gitlab::Metrics.counter(name, options.docs, options.base_labels)
+ Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
when :histogram
- options[:buckets] ||= ::Prometheus::Client::Histogram::DEFAULT_BUCKETS
- Gitlab::Metrics.histogram(name, options.docs, options.base_labels, options.buckets)
+ Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
when :summary
raise NotImplementedError, "summary metrics are not currently supported"
else
@@ -58,12 +61,6 @@ module Gitlab
end
end
- counter :global do
- docstring "Global counter"
- multiprocess_mode :all
- buckets [0, 1]
- end
-
# Fetch and/or initialize counter metric
# @param [Symbol] name
# @param [Hash] opts
diff --git a/lib/gitlab/metrics/concern/metric_options.rb b/lib/gitlab/metrics/concern/metric_options.rb
index 502805c135a..996757e11d5 100644
--- a/lib/gitlab/metrics/concern/metric_options.rb
+++ b/lib/gitlab/metrics/concern/metric_options.rb
@@ -6,34 +6,36 @@ module Gitlab
@multiprocess_mode = options[:multiprocess_mode] || :all
@buckets = options[:buckets] || ::Prometheus::Client::Histogram::DEFAULT_BUCKETS
@base_labels = options[:base_labels] || {}
+ @docstring = options[:docstring]
+ @with_feature = options[:with_feature]
end
- def docs(docs = nil)
- @docs = docs unless docs.nil?
+ def docstring(docstring = nil)
+ @docstring = docstring unless docstring.nil?
- @docs
+ @docstring
end
- def multiprocess_mode(mode)
- @multiprocess_mode = mode unless @multiprocess_mode.nil?
+ def multiprocess_mode(mode = nil)
+ @multiprocess_mode = mode unless mode.nil?
@multiprocess_mode
end
- def buckets(buckets)
- @buckets = buckets unless @buckets.nil?
+ def buckets(buckets = nil)
+ @buckets = buckets unless buckets.nil?
@buckets
end
- def base_labels(base_labels)
- @base_labels = base_labels unless @base_labels.nil?
+ def base_labels(base_labels = nil)
+ @base_labels = base_labels unless base_labels.nil?
@base_labels
end
- def with_feature(name)
- @feature_name = name unless @feature_name.nil?
+ def with_feature(name = nil)
+ @feature_name = name unless name.nil?
@feature_name
end
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index 5cd5c388fb6..7cb153237d4 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -8,10 +8,11 @@ module Gitlab
BASE_LABELS = { module: nil, method: nil }.freeze
attr_reader :real_time, :cpu_time, :call_count, :labels
- histogram :gitlab_method_call_duration_seconds, 'Method calls real duration',
- base_labels: Transaction::BASE_LABELS.merge(BASE_LABELS),
- buckets: [0.01, 0.05, 0.1, 0.5, 1],
- with_feature: :prometheus_metrics_method_instrumentation
+ define_histogram :gitlab_method_call_duration_seconds,
+ docstring: 'Method calls real duration',
+ base_labels: Transaction::BASE_LABELS.merge(BASE_LABELS),
+ buckets: [0.01, 0.05, 0.1, 0.5, 1],
+ with_feature: :prometheus_metrics_method_instrumentation
# name - The full name of the method (including namespace) such as
# `User#sign_in`.
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 8d979a1ef72..41acfb5e819 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -2,11 +2,12 @@ module Gitlab
module Metrics
# Class for storing metrics information of a single transaction.
class Transaction
+ include Gitlab::Metrics::Concern
+
# base labels shared among all transactions
BASE_LABELS = { controller: nil, action: nil }.freeze
THREAD_KEY = :_gitlab_metrics_transaction
- METRICS_MUTEX = Mutex.new
# The series to store events (e.g. Git pushes) in.
EVENT_SERIES = 'events'.freeze
@@ -136,27 +137,25 @@ module Gitlab
"#{labels[:controller]}##{labels[:action]}" if labels && !labels.empty?
end
- histogram :gitlab_transaction_duration_seconds, 'Transaction duration',
- base_labels: BASE_LABELS,
- buckets: [0.001, 0.01, 0.1, 0.5, 10.0],
- with_feature: :prometheus_metrics_method_instrumentation
+ define_histogram :gitlab_transaction_duration_seconds,
+ docstring: 'Transaction duration',
+ base_labels: BASE_LABELS,
+ buckets: [0.001, 0.01, 0.1, 0.5, 10.0],
+ with_feature: :prometheus_metrics_method_instrumentation
- histogram :gitlab_transaction_allocated_memory_bytes, 'Transaction allocated memory bytes',
- base_labels: BASE_LABELS,
- buckets: [100, 1000, 10000, 100000, 1000000, 10000000]
+ define_histogram :gitlab_transaction_allocated_memory_bytes,
+ docstring: 'Transaction allocated memory bytes',
+ base_labels: BASE_LABELS,
+ buckets: [100, 1000, 10000, 100000, 1000000, 10000000]
def self.transaction_metric(name, type, prefix: nil, tags: {})
- return @transaction_metric[name] if @transaction_metric[name]&.has_key?(name)
-
- METRICS_MUTEX.synchronize do
- @transaction_metric ||= {}
- @transaction_metric[name] ||= if type == :counter
- Gitlab::Metrics.counter("gitlab_transaction_#{prefix}#{name}_total".to_sym,
- "Transaction #{prefix}#{name} counter", tags.merge(BASE_LABELS))
- else
- Gitlab::Metrics.gauge("gitlab_transaction_#{name}".to_sym,
- "Transaction gauge #{name} ", tags.merge(BASE_LABELS), :livesum)
- end
+ metric_name = "gitlab_transaction_#{prefix}#{name}_total".to_sym
+ fetch_metric(type, metric_name) do
+ docstring "Transaction #{prefix}#{name} #{type}"
+ base_labels tags.merge(BASE_LABELS)
+ if type == :gauge
+ multiprocess_mode :livesum
+ end
end
end
end
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index fcdd456613c..e3e594a75e4 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Metrics::MethodCall do
describe '#measure' do
before do
- described_class.reload_gitlab_method_call_duration_seconds!
+ described_class.reload_metric!(:gitlab_method_call_duration_seconds)
end
it 'measures the performance of the supplied block' do
--
cgit v1.2.1
From 62ef34cb400eeacdd5d1da0d8edee97d6f742ecc Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Tue, 16 Jan 2018 16:08:12 +0100
Subject: Put View instrumentation and transaction memory use behind feature
---
lib/gitlab/metrics/subscribers/action_view.rb | 10 ++++++----
lib/gitlab/metrics/transaction.rb | 6 +++---
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index 5c390a077c2..2e3beb05294 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -4,9 +4,11 @@ module Gitlab
# Class for tracking the rendering timings of views.
class ActionView < ActiveSupport::Subscriber
include Gitlab::Metrics::Concern
- histogram :gitlab_view_rendering_duration_seconds, 'View rendering time',
- base_labels: Transaction::BASE_LABELS.merge({ path: nil }),
- buckets: [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
+ define_histogram :gitlab_view_rendering_duration_seconds,
+ docstring: 'View rendering time',
+ base_labels: Transaction::BASE_LABELS.merge({ path: nil }),
+ buckets: [0.001, 0.01, 0.1, 10.0],
+ with_feature: :prometheus_metrics_view_instrumentation
attach_to :action_view
@@ -24,7 +26,7 @@ module Gitlab
values = values_for(event)
tags = tags_for(event)
- gitlab_view_rendering_duration_seconds.observe(current_transaction.labels.merge(tags), event.duration)
+ self.class.gitlab_view_rendering_duration_seconds.observe(current_transaction.labels.merge(tags), event.duration)
current_transaction.increment(:view_duration, event.duration)
current_transaction.add_metric(SERIES, values, tags)
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 41acfb5e819..3001dea1441 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -140,13 +140,13 @@ module Gitlab
define_histogram :gitlab_transaction_duration_seconds,
docstring: 'Transaction duration',
base_labels: BASE_LABELS,
- buckets: [0.001, 0.01, 0.1, 0.5, 10.0],
- with_feature: :prometheus_metrics_method_instrumentation
+ buckets: [0.001, 0.01, 0.1, 0.5, 10.0]
define_histogram :gitlab_transaction_allocated_memory_bytes,
docstring: 'Transaction allocated memory bytes',
base_labels: BASE_LABELS,
- buckets: [100, 1000, 10000, 100000, 1000000, 10000000]
+ buckets: [100, 1000, 10000, 100000, 1000000, 10000000],
+ with_feature: :prometheus_metrics_transaction_allocated_memory
def self.transaction_metric(name, type, prefix: nil, tags: {})
metric_name = "gitlab_transaction_#{prefix}#{name}_total".to_sym
--
cgit v1.2.1
From 5328584b1eb8209145685725e2948b4f51a58059 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Tue, 16 Jan 2018 16:30:17 +0100
Subject: Reduce cardinality and convert some metrics to new scheme
---
lib/gitlab/metrics/influx_db.rb | 37 ++++++++++++++-----------
lib/gitlab/metrics/subscribers/active_record.rb | 2 +-
2 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index ef44a13df51..b57d111b853 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -1,6 +1,7 @@
module Gitlab
module Metrics
module InfluxDb
+ include Gitlab::Metrics::Concern
include Gitlab::CurrentSettings
extend self
@@ -12,7 +13,7 @@ module Gitlab
end
# Prometheus histogram buckets used for arbitrary code measurements
- EXECUTION_MEASUREMENT_BUCKETS = [0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1].freeze
+ EXECUTION_MEASUREMENT_BUCKETS = [0.001, 0.01, 0.1, 1].freeze
RAILS_ROOT = Rails.root.to_s
METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
PATH_REGEX = /^#{RAILS_ROOT}\/?/
@@ -105,21 +106,25 @@ module Gitlab
real_time = (real_stop - real_start)
cpu_time = cpu_stop - cpu_start
- Gitlab::Metrics.histogram("gitlab_#{name}_real_duration_seconds".to_sym,
- "Measure #{name}",
- Transaction::BASE_LABELS,
- EXECUTION_MEASUREMENT_BUCKETS)
- .observe(trans.labels, real_time)
-
- Gitlab::Metrics.histogram("gitlab_#{name}_cpu_duration_seconds".to_sym,
- "Measure #{name}",
- Transaction::BASE_LABELS,
- EXECUTION_MEASUREMENT_BUCKETS)
- .observe(trans.labels, cpu_time / 1000.0)
-
- # InfluxDB stores the _real_time time values as milliseconds
- trans.increment("#{name}_real_time", real_time * 1000, false)
- trans.increment("#{name}_cpu_time", cpu_time, false)
+ real_duration_seconds = self.class.fetch_histogram("gitlab_#{name}_real_duration_seconds".to_sym) do
+ docstring "Measure #{name}"
+ base_labels Transaction::BASE_LABELS
+ buckets EXECUTION_MEASUREMENT_BUCKETS
+ end
+
+ real_duration_seconds.observe(trans.labels, real_time)
+
+ cpu_duration_seconds = self.class.fetch_histogram("gitlab_#{name}_cpu_duration_seconds".to_sym) do
+ docstring "Measure #{name}"
+ base_labels Transaction::BASE_LABELS
+ buckets EXECUTION_MEASUREMENT_BUCKETS
+ # with_feature "prometheus_metrics_measure_#{name}_cpu_duration"
+ end
+ cpu_duration_seconds.observe(trans.labels, cpu_time)
+
+ # InfluxDB stores the _real_time and _cpu_time time values as milliseconds
+ trans.increment("#{name}_real_time", real_time.in_milliseconds, false)
+ trans.increment("#{name}_cpu_time", cpu_time.in_milliseconds, false)
trans.increment("#{name}_call_count", 1, false)
retval
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index ead1acb8d44..faf86f08516 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -25,7 +25,7 @@ module Gitlab
:gitlab_sql_duration_seconds,
'SQL time',
Transaction::BASE_LABELS,
- [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.500, 2.0, 10.0]
+ [0.001, 0.01, 0.1, 10.0]
)
end
end
--
cgit v1.2.1
From 66c2a3a83e2c8a15302213141923723295d0d647 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Tue, 16 Jan 2018 20:03:30 +0100
Subject: Fix helper methods and document metric builder options
---
lib/gitlab/metrics/concern.rb | 8 ++++----
lib/gitlab/metrics/concern/metric_options.rb | 9 +++++++++
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index e7c91df94bc..5d465232726 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -65,28 +65,28 @@ module Gitlab
# @param [Symbol] name
# @param [Hash] opts
def fetch_counter(name, opts = {}, &block)
- define_metric(:counter, name, opts, &block)
+ fetch_metric(:counter, name, opts, &block)
end
# DFetch and/or initialize gauge metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_gauge(name, opts = {}, &block)
- define_metric(:counter, name, opts, &block)
+ fetch_metric(:counter, name, opts, &block)
end
# Fetch and/or initialize histogram metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_histogram(name, opts = {}, &block)
- define_metric(:histogram, name, opts, &block)
+ fetch_metric(:histogram, name, opts, &block)
end
# Fetch and/or initialize summary metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_summary(name, opts = {}, &block)
- define_metric(:summary, name, opts, &block)
+ fetch_metric(:summary, name, opts, &block)
end
# Define metric accessor method for a Counter
diff --git a/lib/gitlab/metrics/concern/metric_options.rb b/lib/gitlab/metrics/concern/metric_options.rb
index 996757e11d5..dc964512912 100644
--- a/lib/gitlab/metrics/concern/metric_options.rb
+++ b/lib/gitlab/metrics/concern/metric_options.rb
@@ -10,30 +10,39 @@ module Gitlab
@with_feature = options[:with_feature]
end
+ # Documentation describing metric in metrics endpoint '/-/metrics'
def docstring(docstring = nil)
@docstring = docstring unless docstring.nil?
@docstring
end
+ # Gauge aggregation mode for multiprocess metrics
+ # - :all (default) returns each gauge for every process
+ # - :livesum all process'es gauges summed up
+ # - :max maximum value of per process gauges
+ # - :min minimum value of per process gauges
def multiprocess_mode(mode = nil)
@multiprocess_mode = mode unless mode.nil?
@multiprocess_mode
end
+ # Measurement buckets for histograms
def buckets(buckets = nil)
@buckets = buckets unless buckets.nil?
@buckets
end
+ # Base labels are merged with per metric labels
def base_labels(base_labels = nil)
@base_labels = base_labels unless base_labels.nil?
@base_labels
end
+ # Use feature toggle to control whether certain metric is enabled/disabled
def with_feature(name = nil)
@feature_name = name unless name.nil?
--
cgit v1.2.1
From 91a991367e7d5dfc1c3488ade3812635bd43531c Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Tue, 16 Jan 2018 20:04:10 +0100
Subject: Convert active record
---
lib/gitlab/metrics/subscribers/active_record.rb | 18 ++++++++----------
.../gitlab/metrics/subscribers/active_record_spec.rb | 2 +-
2 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index faf86f08516..bdcbd873001 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -3,12 +3,13 @@ module Gitlab
module Subscribers
# Class for tracking the total query duration of a transaction.
class ActiveRecord < ActiveSupport::Subscriber
+ include Gitlab::Metrics::Concern
attach_to :active_record
def sql(event)
return unless current_transaction
- metric_sql_duration_seconds.observe(current_transaction.labels, event.duration / 1000.0)
+ self.class.gitlab_sql_duration_seconds.observe(current_transaction.labels, event.duration / 1000.0)
current_transaction.increment(:sql_duration, event.duration, false)
current_transaction.increment(:sql_count, 1, false)
@@ -16,17 +17,14 @@ module Gitlab
private
- def current_transaction
- Transaction.current
+ define_histogram :gitlab_sql_duration_seconds do
+ docstring 'SQL time'
+ base_labels Transaction::BASE_LABELS
+ buckets [0.001, 0.01, 0.1, 10.0]
end
- def metric_sql_duration_seconds
- @metric_sql_duration_seconds ||= Gitlab::Metrics.histogram(
- :gitlab_sql_duration_seconds,
- 'SQL time',
- Transaction::BASE_LABELS,
- [0.001, 0.01, 0.1, 10.0]
- )
+ def current_transaction
+ Transaction.current
end
end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 9b3698fb4a8..4e7bd433a9c 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
expect(subscriber).to receive(:current_transaction)
.at_least(:once)
.and_return(transaction)
- expect(subscriber.send(:metric_sql_duration_seconds)).to receive(:observe).with({}, 0.002)
+ expect(described_class.send(:gitlab_sql_duration_seconds)).to receive(:observe).with({}, 0.002)
subscriber.sql(event)
end
--
cgit v1.2.1
From 0325c23732b94bd26647d695ebda38b7077e3924 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Wed, 17 Jan 2018 02:49:23 +0100
Subject: Fix tests
---
lib/gitlab/metrics/influx_db.rb | 6 +++---
spec/lib/gitlab/metrics/subscribers/action_view_spec.rb | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index b57d111b853..e051e292bb1 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -1,7 +1,7 @@
module Gitlab
module Metrics
module InfluxDb
- include Gitlab::Metrics::Concern
+ include Gitlab::Metrics::Concern::ClassMethods
include Gitlab::CurrentSettings
extend self
@@ -106,7 +106,7 @@ module Gitlab
real_time = (real_stop - real_start)
cpu_time = cpu_stop - cpu_start
- real_duration_seconds = self.class.fetch_histogram("gitlab_#{name}_real_duration_seconds".to_sym) do
+ real_duration_seconds = fetch_histogram("gitlab_#{name}_real_duration_seconds".to_sym) do
docstring "Measure #{name}"
base_labels Transaction::BASE_LABELS
buckets EXECUTION_MEASUREMENT_BUCKETS
@@ -114,7 +114,7 @@ module Gitlab
real_duration_seconds.observe(trans.labels, real_time)
- cpu_duration_seconds = self.class.fetch_histogram("gitlab_#{name}_cpu_duration_seconds".to_sym) do
+ cpu_duration_seconds = fetch_histogram("gitlab_#{name}_cpu_duration_seconds".to_sym) do
docstring "Measure #{name}"
base_labels Transaction::BASE_LABELS
buckets EXECUTION_MEASUREMENT_BUCKETS
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index eca75a4fac1..9f3af1acef7 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::Metrics::Subscribers::ActionView do
end
it 'observes view rendering time' do
- expect(subscriber.send(:metric_view_rendering_duration_seconds))
+ expect(described_class.gitlab_view_rendering_duration_seconds)
.to receive(:observe)
.with({ view: 'app/views/x.html.haml' }, 2.1)
--
cgit v1.2.1
From 087c9a5e8f5115170dd0a26470956fd33d48ebb5 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Wed, 17 Jan 2018 02:55:52 +0100
Subject: Fix rubocop warnings
---
lib/gitlab/metrics/concern.rb | 36 ++++++++++++++++++------------------
lib/gitlab/metrics/transaction.rb | 1 +
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 5d465232726..80a87059f46 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -4,13 +4,13 @@ module Gitlab
extend ActiveSupport::Concern
included do
- @@_metrics_provider_mutex ||= Mutex.new
- @@_metrics_provider_cache ||= {}
+ @_metrics_provider_mutex ||= Mutex.new
+ @_metrics_provider_cache ||= {}
end
class_methods do
def reload_metric!(name)
- @@_metrics_provider_cache.delete(name)
+ @_metrics_provider_cache.delete(name)
end
private
@@ -22,7 +22,7 @@ module Gitlab
define_singleton_method(name) do
# avoid unnecessary method call to speed up metric access
- return @@_metrics_provider_cache[name] if @@_metrics_provider_cache.has_key?(name)
+ return @_metrics_provider_cache[name] if @_metrics_provider_cache.has_key?(name)
fetch_metric(type, name, opts, &block)
end
@@ -30,16 +30,16 @@ module Gitlab
def fetch_metric(type, name, opts = {}, &block)
# avoid synchronization to speed up metrics access
- return @@_metrics_provider_cache[name] if @@_metrics_provider_cache.has_key?(name)
+ return @_metrics_provider_cache[name] if @_metrics_provider_cache.has_key?(name)
options = MetricOptions.new(opts)
options.evaluate(&block)
- @@_metrics_provider_mutex.synchronize do
- @@_metrics_provider_cache[name] ||= build_metric!(type, name, options)
+ @_metrics_provider_mutex.synchronize do
+ @_metrics_provider_cache[name] ||= build_metric!(type, name, options)
end
- @@_metrics_provider_cache[name]
+ @_metrics_provider_cache[name]
end
def build_metric!(type, name, options)
@@ -48,16 +48,16 @@ module Gitlab
end
case type
- when :gauge
- Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
- when :counter
- Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
- when :histogram
- Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
- when :summary
- raise NotImplementedError, "summary metrics are not currently supported"
- else
- raise ArgumentError, "uknown metric type #{type}"
+ when :gauge
+ Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
+ when :counter
+ Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
+ when :histogram
+ Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
+ when :summary
+ raise NotImplementedError, "summary metrics are not currently supported"
+ else
+ raise ArgumentError, "uknown metric type #{type}"
end
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 3001dea1441..ed5a2cec935 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -153,6 +153,7 @@ module Gitlab
fetch_metric(type, metric_name) do
docstring "Transaction #{prefix}#{name} #{type}"
base_labels tags.merge(BASE_LABELS)
+
if type == :gauge
multiprocess_mode :livesum
end
--
cgit v1.2.1
From d7edc4b46b1f7a2389d4d0fb58aae9e4ff354535 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Wed, 17 Jan 2018 19:15:50 +0100
Subject: Use Global Mutex, and class instance variables using ```
metric = @_metrics_provider_cache&.[](name) return metric if metric
`` acccessor that only slightly slower (<20ns) than direct class variable
access
---
lib/gitlab/metrics/concern.rb | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 80a87059f46..fa04aee78ba 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -2,15 +2,11 @@ module Gitlab
module Metrics
module Concern
extend ActiveSupport::Concern
-
- included do
- @_metrics_provider_mutex ||= Mutex.new
- @_metrics_provider_cache ||= {}
- end
+ MUTEX = Mutex.new
class_methods do
def reload_metric!(name)
- @_metrics_provider_cache.delete(name)
+ @_metrics_provider_cache&.delete(name)
end
private
@@ -22,7 +18,8 @@ module Gitlab
define_singleton_method(name) do
# avoid unnecessary method call to speed up metric access
- return @_metrics_provider_cache[name] if @_metrics_provider_cache.has_key?(name)
+ metric = @_metrics_provider_cache&.[](name)
+ return metric if metric
fetch_metric(type, name, opts, &block)
end
@@ -30,12 +27,14 @@ module Gitlab
def fetch_metric(type, name, opts = {}, &block)
# avoid synchronization to speed up metrics access
- return @_metrics_provider_cache[name] if @_metrics_provider_cache.has_key?(name)
+ metric = @_metrics_provider_cache&.[](name)
+ return metric if metric
options = MetricOptions.new(opts)
options.evaluate(&block)
- @_metrics_provider_mutex.synchronize do
+ MUTEX.synchronize do
+ @_metrics_provider_cache ||= {}
@_metrics_provider_cache[name] ||= build_metric!(type, name, options)
end
--
cgit v1.2.1
From e3fe65543b9e660ba069899ae9e04fa9850b0f6c Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Wed, 17 Jan 2018 22:16:25 +0100
Subject: Add Tests for Metrics::Concern
---
lib/gitlab/metrics/concern.rb | 6 +-
spec/lib/gitlab/metrics/concern_spec.rb | 101 ++++++++++++++++++++++++++++++++
2 files changed, 104 insertions(+), 3 deletions(-)
create mode 100644 spec/lib/gitlab/metrics/concern_spec.rb
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index fa04aee78ba..6a05fb9b815 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -12,7 +12,7 @@ module Gitlab
private
def define_metric(type, name, opts = {}, &block)
- if instance_methods(false).include?(name)
+ if respond_to?(name)
raise ArgumentError, "metrics method #{name} already exists"
end
@@ -71,7 +71,7 @@ module Gitlab
# @param [Symbol] name
# @param [Hash] opts
def fetch_gauge(name, opts = {}, &block)
- fetch_metric(:counter, name, opts, &block)
+ fetch_metric(:gauge, name, opts, &block)
end
# Fetch and/or initialize histogram metric
@@ -99,7 +99,7 @@ module Gitlab
# @param [Symbol] name
# @param [Hash] opts
def define_gauge(name, opts = {}, &block)
- define_metric(:counter, name, opts, &block)
+ define_metric(:gauge, name, opts, &block)
end
# Define metric accessor method for a Histogram
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
new file mode 100644
index 00000000000..2097396846f
--- /dev/null
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Concern do
+ subject { Class.new { include Gitlab::Metrics::Concern } }
+
+ let(:null_metric) { Gitlab::Metrics::NullMetric.new }
+
+ shared_context 'metric' do |metric_type, *args|
+ let(:docstring) { 'description' }
+ let(:metric) { :sample_metric }
+
+ describe "#define_#{metric_type}" do
+ let(:define_method) { "define_#{metric_type}".to_sym }
+
+ context 'metrics access method not defined' do
+ it "defines metrics accessing method" do
+ expect(subject).not_to respond_to(metric)
+
+ subject.send(define_method, metric, docstring: docstring)
+
+ expect(subject).to respond_to(metric)
+ end
+ end
+
+ context 'metrics access method defined' do
+ before do
+ subject.send(define_method, metric, docstring: docstring)
+ end
+
+ it 'raises error when trying to redefine method' do
+ expect { subject.send(define_method, metric, docstring: docstring) }.to raise_error(ArgumentError)
+ end
+
+ context 'metric is not cached' do
+ it 'calls fetch_metric' do
+ expect(subject).to receive(:fetch_metric).with(metric_type, metric, docstring: docstring)
+
+ subject.send(metric)
+ end
+ end
+
+ context 'metric is cached' do
+ before do
+ subject.send(metric)
+ end
+
+ it 'returns cached metric' do
+ expect(subject).not_to receive(:fetch_metric)
+
+ subject.send(metric)
+ end
+ end
+ end
+ end
+
+ describe "#fetch_#{metric_type}" do
+ let(:fetch_method) { "fetch_#{metric_type}".to_sym }
+
+ context "when #{metric_type} fetched first time" do
+ it 'initializes counter metric' do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ subject.send(fetch_method, metric, docstring: docstring)
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric, docstring, *args)
+ end
+ end
+
+ context "when #{metric_type} is fetched second time" do
+ before do
+ subject.send(fetch_method, metric, docstring: docstring)
+ end
+
+ it 'uses class metric cache' do
+ expect(Gitlab::Metrics).not_to receive(metric_type)
+
+ subject.send(fetch_method, metric, docstring: docstring)
+ end
+
+ context 'when metric is reloaded' do
+ before do
+ subject.reload_metric!(metric)
+ end
+
+ it "initializes #{metric_type} metric" do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ subject.send(fetch_method, metric, docstring: docstring)
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric, docstring, *args)
+ end
+ end
+ end
+ end
+
+ end
+
+ include_examples 'metric', :counter, {}
+ include_examples 'metric', :gauge, {}, :all
+ include_examples 'metric', :histogram, {}, [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
+end
--
cgit v1.2.1
From 5f7aa407a85f536d073d4736baa1cabfc0ccb4ff Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Wed, 17 Jan 2018 22:17:06 +0100
Subject: Small Concern_spec cleanup
---
spec/lib/gitlab/metrics/concern_spec.rb | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
index 2097396846f..de56ac4c275 100644
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -3,8 +3,6 @@ require 'spec_helper'
describe Gitlab::Metrics::Concern do
subject { Class.new { include Gitlab::Metrics::Concern } }
- let(:null_metric) { Gitlab::Metrics::NullMetric.new }
-
shared_context 'metric' do |metric_type, *args|
let(:docstring) { 'description' }
let(:metric) { :sample_metric }
@@ -55,6 +53,7 @@ describe Gitlab::Metrics::Concern do
describe "#fetch_#{metric_type}" do
let(:fetch_method) { "fetch_#{metric_type}".to_sym }
+ let(:null_metric) { Gitlab::Metrics::NullMetric.new }
context "when #{metric_type} fetched first time" do
it 'initializes counter metric' do
@@ -92,7 +91,6 @@ describe Gitlab::Metrics::Concern do
end
end
end
-
end
include_examples 'metric', :counter, {}
--
cgit v1.2.1
From afeb7ce993f2106cb2a6c4e2741356e2ed039d7c Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Wed, 17 Jan 2018 23:21:42 +0100
Subject: Add changelog
---
changelogs/unreleased/41771-reduce-cardinality-of-metrics.yml | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 changelogs/unreleased/41771-reduce-cardinality-of-metrics.yml
diff --git a/changelogs/unreleased/41771-reduce-cardinality-of-metrics.yml b/changelogs/unreleased/41771-reduce-cardinality-of-metrics.yml
new file mode 100644
index 00000000000..f64fd66ef79
--- /dev/null
+++ b/changelogs/unreleased/41771-reduce-cardinality-of-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce the number of Prometheus metrics
+merge_request: 16443
+author:
+type: performance
--
cgit v1.2.1
From 67fcd0610cd19808089faac57562b19b407540be Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Thu, 18 Jan 2018 13:37:53 +0100
Subject: Test if feature is respected
---
lib/gitlab/metrics/concern/metric_options.rb | 4 +-
spec/lib/gitlab/metrics/concern_spec.rb | 71 ++++++++++++++++++++--------
2 files changed, 54 insertions(+), 21 deletions(-)
diff --git a/lib/gitlab/metrics/concern/metric_options.rb b/lib/gitlab/metrics/concern/metric_options.rb
index dc964512912..a5fcdcdb9ca 100644
--- a/lib/gitlab/metrics/concern/metric_options.rb
+++ b/lib/gitlab/metrics/concern/metric_options.rb
@@ -44,9 +44,9 @@ module Gitlab
# Use feature toggle to control whether certain metric is enabled/disabled
def with_feature(name = nil)
- @feature_name = name unless name.nil?
+ @with_feature = name unless name.nil?
- @feature_name
+ @with_feature
end
def evaluate(&block)
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
index de56ac4c275..fc5f19000ba 100644
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -5,47 +5,47 @@ describe Gitlab::Metrics::Concern do
shared_context 'metric' do |metric_type, *args|
let(:docstring) { 'description' }
- let(:metric) { :sample_metric }
+ let(:metric_name) { :sample_metric }
describe "#define_#{metric_type}" do
let(:define_method) { "define_#{metric_type}".to_sym }
context 'metrics access method not defined' do
it "defines metrics accessing method" do
- expect(subject).not_to respond_to(metric)
+ expect(subject).not_to respond_to(metric_name)
- subject.send(define_method, metric, docstring: docstring)
+ subject.send(define_method, metric_name, docstring: docstring)
- expect(subject).to respond_to(metric)
+ expect(subject).to respond_to(metric_name)
end
end
context 'metrics access method defined' do
before do
- subject.send(define_method, metric, docstring: docstring)
+ subject.send(define_method, metric_name, docstring: docstring)
end
it 'raises error when trying to redefine method' do
- expect { subject.send(define_method, metric, docstring: docstring) }.to raise_error(ArgumentError)
+ expect { subject.send(define_method, metric_name, docstring: docstring) }.to raise_error(ArgumentError)
end
context 'metric is not cached' do
it 'calls fetch_metric' do
- expect(subject).to receive(:fetch_metric).with(metric_type, metric, docstring: docstring)
+ expect(subject).to receive(:fetch_metric).with(metric_type, metric_name, docstring: docstring)
- subject.send(metric)
+ subject.send(metric_name)
end
end
context 'metric is cached' do
before do
- subject.send(metric)
+ subject.send(metric_name)
end
it 'returns cached metric' do
expect(subject).not_to receive(:fetch_metric)
- subject.send(metric)
+ subject.send(metric_name)
end
end
end
@@ -55,38 +55,71 @@ describe Gitlab::Metrics::Concern do
let(:fetch_method) { "fetch_#{metric_type}".to_sym }
let(:null_metric) { Gitlab::Metrics::NullMetric.new }
- context "when #{metric_type} fetched first time" do
+ context "when #{metric_type} is not cached" do
it 'initializes counter metric' do
allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
- subject.send(fetch_method, metric, docstring: docstring)
+ subject.send(fetch_method, metric_name, docstring: docstring)
- expect(Gitlab::Metrics).to have_received(metric_type).with(metric, docstring, *args)
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
end
end
- context "when #{metric_type} is fetched second time" do
+ context "when #{metric_type} is cached" do
before do
- subject.send(fetch_method, metric, docstring: docstring)
+ subject.send(fetch_method, metric_name, docstring: docstring)
end
it 'uses class metric cache' do
expect(Gitlab::Metrics).not_to receive(metric_type)
- subject.send(fetch_method, metric, docstring: docstring)
+ subject.send(fetch_method, metric_name, docstring: docstring)
end
context 'when metric is reloaded' do
before do
- subject.reload_metric!(metric)
+ subject.reload_metric!(metric_name)
end
it "initializes #{metric_type} metric" do
allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
- subject.send(fetch_method, metric, docstring: docstring)
+ subject.send(fetch_method, metric_name, docstring: docstring)
- expect(Gitlab::Metrics).to have_received(metric_type).with(metric, docstring, *args)
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+ end
+
+ context 'when metric is configured with feature' do
+ let(:feature_name) { :some_metric_feature }
+ let(:metric) { subject.send(fetch_method, metric_name, docstring: docstring, with_feature: feature_name) }
+
+ context 'when feature is enabled' do
+ before do
+ Feature.get(feature_name).enable
+ end
+
+ it "initializes #{metric_type} metric" do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ metric
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ Feature.get(feature_name).disable
+ end
+
+ it "returns NullMetric" do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ expect(metric).to be_instance_of(Gitlab::Metrics::NullMetric)
+
+ expect(Gitlab::Metrics).not_to have_received(metric_type)
end
end
end
--
cgit v1.2.1
From 6ff9f028fd09b5ebd941f63a1aecc134741ef27e Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Thu, 18 Jan 2018 13:49:33 +0100
Subject: Migrate and reduce the buckets of Gitaly metrics /cc @bjk-gitlab /cc
@zj
---
lib/gitlab/gitaly_client.rb | 33 ++++++++--------------------
lib/gitlab/metrics/concern/metric_options.rb | 4 +++-
2 files changed, 12 insertions(+), 25 deletions(-)
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 6bd256f57c7..583ef1c19e1 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -6,6 +6,7 @@ require 'grpc/health/v1/health_services_pb'
module Gitlab
module GitalyClient
+ include Gitlab::Metrics::Concern
module MigrationStatus
DISABLED = 1
OPT_IN = 2
@@ -33,8 +34,6 @@ module Gitlab
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new
- METRICS_MUTEX = Mutex.new
- private_constant :MUTEX, :METRICS_MUTEX
class << self
attr_accessor :query_time
@@ -42,28 +41,14 @@ module Gitlab
self.query_time = 0
- def self.migrate_histogram
- @migrate_histogram ||=
- METRICS_MUTEX.synchronize do
- # If a thread was blocked on the mutex, the value was set already
- return @migrate_histogram if @migrate_histogram
-
- Gitlab::Metrics.histogram(:gitaly_migrate_call_duration_seconds,
- "Gitaly migration call execution timings",
- gitaly_enabled: nil, feature: nil)
- end
+ define_histogram :gitaly_migrate_call_duration_seconds do
+ docstring "Gitaly migration call execution timings"
+ base_labels gitaly_enabled: nil, feature: nil
end
- def self.gitaly_call_histogram
- @gitaly_call_histogram ||=
- METRICS_MUTEX.synchronize do
- # If a thread was blocked on the mutex, the value was set already
- return @gitaly_call_histogram if @gitaly_call_histogram
-
- Gitlab::Metrics.histogram(:gitaly_controller_action_duration_seconds,
- "Gitaly endpoint histogram by controller and action combination",
- Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil))
- end
+ define_histogram :gitaly_controller_action_duration_seconds do
+ docstring "Gitaly endpoint histogram by controller and action combination"
+ base_labels Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil)
end
def self.stub(name, storage)
@@ -145,7 +130,7 @@ module Gitlab
# Keep track, seperately, for the performance bar
self.query_time += duration
- gitaly_call_histogram.observe(
+ gitaly_controller_action_duration_seconds.observe(
current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
duration)
end
@@ -247,7 +232,7 @@ module Gitlab
yield is_enabled
ensure
total_time = Gitlab::Metrics::System.monotonic_time - start
- migrate_histogram.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
+ gitaly_migrate_call_duration_seconds.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
feature_stack.shift
Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty?
end
diff --git a/lib/gitlab/metrics/concern/metric_options.rb b/lib/gitlab/metrics/concern/metric_options.rb
index a5fcdcdb9ca..1f860b9b77f 100644
--- a/lib/gitlab/metrics/concern/metric_options.rb
+++ b/lib/gitlab/metrics/concern/metric_options.rb
@@ -2,9 +2,11 @@ module Gitlab
module Metrics
module Concern
class MetricOptions
+ SMALL_NETWORK_BUCKETS = [0.005, 0.01, 0.1, 1, 10].freeze
+
def initialize(options = {})
@multiprocess_mode = options[:multiprocess_mode] || :all
- @buckets = options[:buckets] || ::Prometheus::Client::Histogram::DEFAULT_BUCKETS
+ @buckets = options[:buckets] || SMALL_NETWORK_BUCKETS
@base_labels = options[:base_labels] || {}
@docstring = options[:docstring]
@with_feature = options[:with_feature]
--
cgit v1.2.1
From 3e898be8aa80993a8af016b351c796beb8750d2e Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Thu, 18 Jan 2018 20:22:49 +0100
Subject: Avoid cascading locking
---
lib/gitlab/metrics/concern.rb | 18 ++++++++++++------
spec/lib/gitlab/metrics/concern_spec.rb | 2 +-
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 6a05fb9b815..11852f50319 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -33,19 +33,25 @@ module Gitlab
options = MetricOptions.new(opts)
options.evaluate(&block)
+ if disabled_by_feature(options)
+ synchronized_cache_fill(name) { NullMetric.new }
+ else
+ synchronized_cache_fill(name) { build_metric!(type, name, options) }
+ end
+ end
+
+ def synchronized_cache_fill(key)
MUTEX.synchronize do
@_metrics_provider_cache ||= {}
- @_metrics_provider_cache[name] ||= build_metric!(type, name, options)
+ @_metrics_provider_cache[key] ||= yield
end
+ end
- @_metrics_provider_cache[name]
+ def disabled_by_feature(options)
+ options.with_feature && !Feature.get(options.with_feature).enabled?
end
def build_metric!(type, name, options)
- unless options.with_feature.nil? || Feature.get(options.with_feature).enabled?
- return NullMetric.new
- end
-
case type
when :gauge
Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
index fc5f19000ba..1f750397750 100644
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -128,5 +128,5 @@ describe Gitlab::Metrics::Concern do
include_examples 'metric', :counter, {}
include_examples 'metric', :gauge, {}, :all
- include_examples 'metric', :histogram, {}, [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
+ include_examples 'metric', :histogram, {}, [0.005, 0.01, 0.1, 1, 10]
end
--
cgit v1.2.1
From f5383578d92d525881bae909740306c57fb1cca0 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Fri, 19 Jan 2018 14:54:06 +0100
Subject: NullMetric as a singleton
---
lib/gitlab/metrics/concern.rb | 2 +-
lib/gitlab/metrics/null_metric.rb | 2 ++
lib/gitlab/metrics/prometheus.rb | 2 +-
spec/lib/gitlab/metrics/concern_spec.rb | 4 +++-
4 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 11852f50319..95a50d8b461 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -34,7 +34,7 @@ module Gitlab
options.evaluate(&block)
if disabled_by_feature(options)
- synchronized_cache_fill(name) { NullMetric.new }
+ synchronized_cache_fill(name) { NullMetric.instance }
else
synchronized_cache_fill(name) { build_metric!(type, name, options) }
end
diff --git a/lib/gitlab/metrics/null_metric.rb b/lib/gitlab/metrics/null_metric.rb
index 3b5a2907195..aabada5c21a 100644
--- a/lib/gitlab/metrics/null_metric.rb
+++ b/lib/gitlab/metrics/null_metric.rb
@@ -2,6 +2,8 @@ module Gitlab
module Metrics
# Mocks ::Prometheus::Client::Metric and all derived metrics
class NullMetric
+ include Singleton
+
def method_missing(name, *args, &block)
nil
end
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index b0b8e8436db..a697e61dd6b 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -64,7 +64,7 @@ module Gitlab
if prometheus_metrics_enabled?
registry.get(name)
else
- NullMetric.new
+ NullMetric.instance
end
end
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
index 1f750397750..9d0eddc7355 100644
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -53,7 +53,9 @@ describe Gitlab::Metrics::Concern do
describe "#fetch_#{metric_type}" do
let(:fetch_method) { "fetch_#{metric_type}".to_sym }
- let(:null_metric) { Gitlab::Metrics::NullMetric.new }
+ let(:_metric_type) { metric_type }
+
+ let(:null_metric) { Gitlab::Metrics::NullMetric.instance }
context "when #{metric_type} is not cached" do
it 'initializes counter metric' do
--
cgit v1.2.1
From 83ae17214552e74960654bf9073aad1d3ade9193 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Fri, 19 Jan 2018 14:54:40 +0100
Subject: Cleanup rspec
---
lib/gitlab/metrics/concern.rb | 16 ++++++----------
spec/lib/gitlab/metrics/concern_spec.rb | 34 +++++++++++++++++++--------------
2 files changed, 26 insertions(+), 24 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 95a50d8b461..304a352ec53 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -13,23 +13,19 @@ module Gitlab
def define_metric(type, name, opts = {}, &block)
if respond_to?(name)
- raise ArgumentError, "metrics method #{name} already exists"
+ raise ArgumentError, "method #{name} already exists"
end
define_singleton_method(name) do
- # avoid unnecessary method call to speed up metric access
- metric = @_metrics_provider_cache&.[](name)
- return metric if metric
-
- fetch_metric(type, name, opts, &block)
+ @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
end
end
def fetch_metric(type, name, opts = {}, &block)
- # avoid synchronization to speed up metrics access
- metric = @_metrics_provider_cache&.[](name)
- return metric if metric
+ @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
+ end
+ def init_metric(type, name, opts = {}, &block)
options = MetricOptions.new(opts)
options.evaluate(&block)
@@ -73,7 +69,7 @@ module Gitlab
fetch_metric(:counter, name, opts, &block)
end
- # DFetch and/or initialize gauge metric
+ # Fetch and/or initialize gauge metric
# @param [Symbol] name
# @param [Hash] opts
def fetch_gauge(name, opts = {}, &block)
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
index 9d0eddc7355..cf2a508fe04 100644
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -8,13 +8,17 @@ describe Gitlab::Metrics::Concern do
let(:metric_name) { :sample_metric }
describe "#define_#{metric_type}" do
- let(:define_method) { "define_#{metric_type}".to_sym }
+ let(:_metric_type) { metric_type }
+
+ def define_metric_method(**args)
+ subject.send("define_#{_metric_type}", metric_name, **args)
+ end
context 'metrics access method not defined' do
it "defines metrics accessing method" do
expect(subject).not_to respond_to(metric_name)
- subject.send(define_method, metric_name, docstring: docstring)
+ define_metric_method(docstring: docstring)
expect(subject).to respond_to(metric_name)
end
@@ -22,16 +26,16 @@ describe Gitlab::Metrics::Concern do
context 'metrics access method defined' do
before do
- subject.send(define_method, metric_name, docstring: docstring)
+ define_metric_method(docstring: docstring)
end
it 'raises error when trying to redefine method' do
- expect { subject.send(define_method, metric_name, docstring: docstring) }.to raise_error(ArgumentError)
+ expect { define_metric_method(docstring: docstring) }.to raise_error(ArgumentError)
end
context 'metric is not cached' do
it 'calls fetch_metric' do
- expect(subject).to receive(:fetch_metric).with(metric_type, metric_name, docstring: docstring)
+ expect(subject).to receive(:init_metric).with(metric_type, metric_name, docstring: docstring)
subject.send(metric_name)
end
@@ -43,7 +47,7 @@ describe Gitlab::Metrics::Concern do
end
it 'returns cached metric' do
- expect(subject).not_to receive(:fetch_metric)
+ expect(subject).not_to receive(:init_metric)
subject.send(metric_name)
end
@@ -52,16 +56,18 @@ describe Gitlab::Metrics::Concern do
end
describe "#fetch_#{metric_type}" do
- let(:fetch_method) { "fetch_#{metric_type}".to_sym }
let(:_metric_type) { metric_type }
-
let(:null_metric) { Gitlab::Metrics::NullMetric.instance }
+ def fetch_metric_method(**args)
+ subject.send("fetch_#{_metric_type}", metric_name, **args)
+ end
+
context "when #{metric_type} is not cached" do
it 'initializes counter metric' do
allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
- subject.send(fetch_method, metric_name, docstring: docstring)
+ fetch_metric_method(docstring: docstring)
expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
end
@@ -69,13 +75,13 @@ describe Gitlab::Metrics::Concern do
context "when #{metric_type} is cached" do
before do
- subject.send(fetch_method, metric_name, docstring: docstring)
+ fetch_metric_method(docstring: docstring)
end
it 'uses class metric cache' do
expect(Gitlab::Metrics).not_to receive(metric_type)
- subject.send(fetch_method, metric_name, docstring: docstring)
+ fetch_metric_method(docstring: docstring)
end
context 'when metric is reloaded' do
@@ -86,7 +92,7 @@ describe Gitlab::Metrics::Concern do
it "initializes #{metric_type} metric" do
allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
- subject.send(fetch_method, metric_name, docstring: docstring)
+ fetch_metric_method(docstring: docstring)
expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
end
@@ -95,7 +101,7 @@ describe Gitlab::Metrics::Concern do
context 'when metric is configured with feature' do
let(:feature_name) { :some_metric_feature }
- let(:metric) { subject.send(fetch_method, metric_name, docstring: docstring, with_feature: feature_name) }
+ let(:metric) { fetch_metric_method(docstring: docstring, with_feature: feature_name) }
context 'when feature is enabled' do
before do
@@ -117,7 +123,7 @@ describe Gitlab::Metrics::Concern do
end
it "returns NullMetric" do
- allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+ allow(Gitlab::Metrics).to receive(metric_type)
expect(metric).to be_instance_of(Gitlab::Metrics::NullMetric)
--
cgit v1.2.1
From 27359dbf22efe1d8cde2039b4e8d0c9199ffbf43 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Fri, 19 Jan 2018 15:04:37 +0100
Subject: use class variable for Mutex instead of Const
---
lib/gitlab/metrics/concern.rb | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 304a352ec53..8b62535d5a5 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -1,8 +1,13 @@
+# rubocop:disable Style/ClassVars
+
module Gitlab
module Metrics
module Concern
extend ActiveSupport::Concern
- MUTEX = Mutex.new
+
+ included do
+ @@_metric_provider_mutex = Mutex.new
+ end
class_methods do
def reload_metric!(name)
@@ -37,7 +42,7 @@ module Gitlab
end
def synchronized_cache_fill(key)
- MUTEX.synchronize do
+ @@_metric_provider_mutex.synchronize do
@_metrics_provider_cache ||= {}
@_metrics_provider_cache[key] ||= yield
end
--
cgit v1.2.1
From ea6196d49078986bd033f415ecd4361a77e044bc Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Fri, 19 Jan 2018 16:30:51 +0100
Subject: Move prometheus metrics to module. Use class instance variables for
metrics and metrics cache in metrics concern
---
lib/gitlab/metrics.rb | 2 +-
lib/gitlab/metrics/concern.rb | 12 +++---
lib/gitlab/metrics/influx_db.rb | 2 +-
lib/gitlab/metrics/prometheus.rb | 93 +++++++++++++++++++++-------------------
4 files changed, 57 insertions(+), 52 deletions(-)
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 4779755bb22..ba849892f72 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -1,7 +1,7 @@
module Gitlab
module Metrics
extend Gitlab::Metrics::InfluxDb
- extend Gitlab::Metrics::Prometheus
+ include Gitlab::Metrics::Prometheus
def self.enabled?
influx_metrics_enabled? || prometheus_metrics_enabled?
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 8b62535d5a5..451dd45c722 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -6,12 +6,13 @@ module Gitlab
extend ActiveSupport::Concern
included do
- @@_metric_provider_mutex = Mutex.new
+ @_metric_provider_mutex = Mutex.new
+ @_metrics_provider_cache = {}
end
class_methods do
def reload_metric!(name)
- @_metrics_provider_cache&.delete(name)
+ @_metrics_provider_cache.delete(name)
end
private
@@ -22,12 +23,12 @@ module Gitlab
end
define_singleton_method(name) do
- @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
+ @_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
end
end
def fetch_metric(type, name, opts = {}, &block)
- @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
+ @_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
end
def init_metric(type, name, opts = {}, &block)
@@ -42,8 +43,7 @@ module Gitlab
end
def synchronized_cache_fill(key)
- @@_metric_provider_mutex.synchronize do
- @_metrics_provider_cache ||= {}
+ @_metric_provider_mutex.synchronize do
@_metrics_provider_cache[key] ||= yield
end
end
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index e051e292bb1..32bf441663a 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -1,7 +1,7 @@
module Gitlab
module Metrics
module InfluxDb
- include Gitlab::Metrics::Concern::ClassMethods
+ include Gitlab::Metrics::Concern
include Gitlab::CurrentSettings
extend self
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index a697e61dd6b..3fd8d0e1414 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -3,73 +3,78 @@ require 'prometheus/client'
module Gitlab
module Metrics
module Prometheus
+ extend ActiveSupport::Concern
+ include Gitlab::Metrics::Concern
include Gitlab::CurrentSettings
- include Gitlab::Utils::StrongMemoize
REGISTRY_MUTEX = Mutex.new
PROVIDER_MUTEX = Mutex.new
- def metrics_folder_present?
- multiprocess_files_dir = ::Prometheus::Client.configuration.multiprocess_files_dir
+ class_methods do
+ include Gitlab::Utils::StrongMemoize
- multiprocess_files_dir &&
- ::Dir.exist?(multiprocess_files_dir) &&
- ::File.writable?(multiprocess_files_dir)
- end
+ def metrics_folder_present?
+ multiprocess_files_dir = ::Prometheus::Client.configuration.multiprocess_files_dir
- def prometheus_metrics_enabled?
- strong_memoize(:prometheus_metrics_enabled) do
- prometheus_metrics_enabled_unmemoized
+ multiprocess_files_dir &&
+ ::Dir.exist?(multiprocess_files_dir) &&
+ ::File.writable?(multiprocess_files_dir)
+ end
+
+ def prometheus_metrics_enabled?
+ strong_memoize(:prometheus_metrics_enabled) do
+ prometheus_metrics_enabled_unmemoized
+ end
end
- end
- def registry
- strong_memoize(:registry) do
- REGISTRY_MUTEX.synchronize do
- strong_memoize(:registry) do
- ::Prometheus::Client.registry
+ def registry
+ strong_memoize(:registry) do
+ REGISTRY_MUTEX.synchronize do
+ strong_memoize(:registry) do
+ ::Prometheus::Client.registry
+ end
end
end
end
- end
- def counter(name, docstring, base_labels = {})
- safe_provide_metric(:counter, name, docstring, base_labels)
- end
+ def counter(name, docstring, base_labels = {})
+ safe_provide_metric(:counter, name, docstring, base_labels)
+ end
- def summary(name, docstring, base_labels = {})
- safe_provide_metric(:summary, name, docstring, base_labels)
- end
+ def summary(name, docstring, base_labels = {})
+ safe_provide_metric(:summary, name, docstring, base_labels)
+ end
- def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
- safe_provide_metric(:gauge, name, docstring, base_labels, multiprocess_mode)
- end
+ def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
+ safe_provide_metric(:gauge, name, docstring, base_labels, multiprocess_mode)
+ end
- def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
- safe_provide_metric(:histogram, name, docstring, base_labels, buckets)
- end
+ def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
+ safe_provide_metric(:histogram, name, docstring, base_labels, buckets)
+ end
- private
+ private
- def safe_provide_metric(method, name, *args)
- metric = provide_metric(name)
- return metric if metric
+ def safe_provide_metric(method, name, *args)
+ metric = provide_metric(name)
+ return metric if metric
- PROVIDER_MUTEX.synchronize do
- provide_metric(name) || registry.method(method).call(name, *args)
+ PROVIDER_MUTEX.synchronize do
+ provide_metric(name) || registry.method(method).call(name, *args)
+ end
end
- end
- def provide_metric(name)
- if prometheus_metrics_enabled?
- registry.get(name)
- else
- NullMetric.instance
+ def provide_metric(name)
+ if prometheus_metrics_enabled?
+ registry.get(name)
+ else
+ NullMetric.instance
+ end
end
- end
- def prometheus_metrics_enabled_unmemoized
- metrics_folder_present? && current_application_settings[:prometheus_metrics_enabled] || false
+ def prometheus_metrics_enabled_unmemoized
+ metrics_folder_present? && current_application_settings[:prometheus_metrics_enabled] || false
+ end
end
end
end
--
cgit v1.2.1
From 7d716cc849ca5435cd5c038bdf924d5a995b6b93 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Fri, 19 Jan 2018 18:12:19 +0100
Subject: Convert InfluxDB to concern. Fix uninitialized metrics when metrics
code is inherited.
---
lib/gitlab/metrics.rb | 2 +-
lib/gitlab/metrics/concern.rb | 9 +-
lib/gitlab/metrics/influx_db.rb | 286 +++++++++++----------
lib/gitlab/metrics/prometheus.rb | 3 +-
.../gitlab/metrics/subscribers/rails_cache_spec.rb | 7 +-
5 files changed, 157 insertions(+), 150 deletions(-)
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index ba849892f72..7d63ca5627d 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -1,6 +1,6 @@
module Gitlab
module Metrics
- extend Gitlab::Metrics::InfluxDb
+ include Gitlab::Metrics::InfluxDb
include Gitlab::Metrics::Prometheus
def self.enabled?
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 451dd45c722..d9d69d8026e 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -6,7 +6,7 @@ module Gitlab
extend ActiveSupport::Concern
included do
- @_metric_provider_mutex = Mutex.new
+ @@_metric_provider_mutex = Mutex.new
@_metrics_provider_cache = {}
end
@@ -23,12 +23,12 @@ module Gitlab
end
define_singleton_method(name) do
- @_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
+ @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
end
end
def fetch_metric(type, name, opts = {}, &block)
- @_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
+ @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
end
def init_metric(type, name, opts = {}, &block)
@@ -43,7 +43,8 @@ module Gitlab
end
def synchronized_cache_fill(key)
- @_metric_provider_mutex.synchronize do
+ @@_metric_provider_mutex.synchronize do
+ @_metrics_provider_cache ||= {}
@_metrics_provider_cache[key] ||= yield
end
end
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 32bf441663a..1fbe030681e 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -1,184 +1,188 @@
module Gitlab
module Metrics
module InfluxDb
+ extend ActiveSupport::Concern
include Gitlab::Metrics::Concern
- include Gitlab::CurrentSettings
- extend self
- MUTEX = Mutex.new
- private_constant :MUTEX
-
- def influx_metrics_enabled?
- settings[:enabled] || false
- end
-
- # Prometheus histogram buckets used for arbitrary code measurements
EXECUTION_MEASUREMENT_BUCKETS = [0.001, 0.01, 0.1, 1].freeze
RAILS_ROOT = Rails.root.to_s
METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
PATH_REGEX = /^#{RAILS_ROOT}\/?/
- def settings
- @settings ||= {
- enabled: current_application_settings[:metrics_enabled],
- pool_size: current_application_settings[:metrics_pool_size],
- timeout: current_application_settings[:metrics_timeout],
- method_call_threshold: current_application_settings[:metrics_method_call_threshold],
- host: current_application_settings[:metrics_host],
- port: current_application_settings[:metrics_port],
- sample_interval: current_application_settings[:metrics_sample_interval] || 15,
- packet_size: current_application_settings[:metrics_packet_size] || 1
- }
- end
+ MUTEX = Mutex.new
+ private_constant :MUTEX
- def mri?
- RUBY_ENGINE == 'ruby'
- end
+ class_methods do
+ include Gitlab::CurrentSettings
- def method_call_threshold
- # This is memoized since this method is called for every instrumented
- # method. Loading data from an external cache on every method call slows
- # things down too much.
- # in milliseconds
- @method_call_threshold ||= settings[:method_call_threshold]
- end
+ def influx_metrics_enabled?
+ settings[:enabled] || false
+ end
- def submit_metrics(metrics)
- prepared = prepare_metrics(metrics)
+ # Prometheus histogram buckets used for arbitrary code measurements
+
+ def settings
+ @settings ||= {
+ enabled: current_application_settings[:metrics_enabled],
+ pool_size: current_application_settings[:metrics_pool_size],
+ timeout: current_application_settings[:metrics_timeout],
+ method_call_threshold: current_application_settings[:metrics_method_call_threshold],
+ host: current_application_settings[:metrics_host],
+ port: current_application_settings[:metrics_port],
+ sample_interval: current_application_settings[:metrics_sample_interval] || 15,
+ packet_size: current_application_settings[:metrics_packet_size] || 1
+ }
+ end
+
+ def mri?
+ RUBY_ENGINE == 'ruby'
+ end
+
+ def method_call_threshold
+ # This is memoized since this method is called for every instrumented
+ # method. Loading data from an external cache on every method call slows
+ # things down too much.
+ # in milliseconds
+ @method_call_threshold ||= settings[:method_call_threshold]
+ end
- pool&.with do |connection|
- prepared.each_slice(settings[:packet_size]) do |slice|
- begin
- connection.write_points(slice)
- rescue StandardError
+ def submit_metrics(metrics)
+ prepared = prepare_metrics(metrics)
+
+ pool&.with do |connection|
+ prepared.each_slice(settings[:packet_size]) do |slice|
+ begin
+ connection.write_points(slice)
+ rescue StandardError
+ end
end
end
+ rescue Errno::EADDRNOTAVAIL, SocketError => ex
+ Gitlab::EnvironmentLogger.error('Cannot resolve InfluxDB address. GitLab Performance Monitoring will not work.')
+ Gitlab::EnvironmentLogger.error(ex)
end
- rescue Errno::EADDRNOTAVAIL, SocketError => ex
- Gitlab::EnvironmentLogger.error('Cannot resolve InfluxDB address. GitLab Performance Monitoring will not work.')
- Gitlab::EnvironmentLogger.error(ex)
- end
- def prepare_metrics(metrics)
- metrics.map do |hash|
- new_hash = hash.symbolize_keys
+ def prepare_metrics(metrics)
+ metrics.map do |hash|
+ new_hash = hash.symbolize_keys
- new_hash[:tags].each do |key, value|
- if value.blank?
- new_hash[:tags].delete(key)
- else
- new_hash[:tags][key] = escape_value(value)
+ new_hash[:tags].each do |key, value|
+ if value.blank?
+ new_hash[:tags].delete(key)
+ else
+ new_hash[:tags][key] = escape_value(value)
+ end
end
- end
- new_hash
+ new_hash
+ end
end
- end
- def escape_value(value)
- value.to_s.gsub('=', '\\=')
- end
-
- # Measures the execution time of a block.
- #
- # Example:
- #
- # Gitlab::Metrics.measure(:find_by_username_duration) do
- # User.find_by_username(some_username)
- # end
- #
- # name - The name of the field to store the execution time in.
- #
- # Returns the value yielded by the supplied block.
- def measure(name)
- trans = current_transaction
-
- return yield unless trans
-
- real_start = Time.now.to_f
- cpu_start = System.cpu_time
-
- retval = yield
-
- cpu_stop = System.cpu_time
- real_stop = Time.now.to_f
-
- real_time = (real_stop - real_start)
- cpu_time = cpu_stop - cpu_start
-
- real_duration_seconds = fetch_histogram("gitlab_#{name}_real_duration_seconds".to_sym) do
- docstring "Measure #{name}"
- base_labels Transaction::BASE_LABELS
- buckets EXECUTION_MEASUREMENT_BUCKETS
+ def escape_value(value)
+ value.to_s.gsub('=', '\\=')
end
- real_duration_seconds.observe(trans.labels, real_time)
+ # Measures the execution time of a block.
+ #
+ # Example:
+ #
+ # Gitlab::Metrics.measure(:find_by_username_duration) do
+ # User.find_by_username(some_username)
+ # end
+ #
+ # name - The name of the field to store the execution time in.
+ #
+ # Returns the value yielded by the supplied block.
+ def measure(name)
+ trans = current_transaction
+
+ return yield unless trans
+
+ real_start = Time.now.to_f
+ cpu_start = System.cpu_time
+
+ retval = yield
+
+ cpu_stop = System.cpu_time
+ real_stop = Time.now.to_f
+
+ real_time = (real_stop - real_start)
+ cpu_time = cpu_stop - cpu_start
+
+ real_duration_seconds = fetch_histogram("gitlab_#{name}_real_duration_seconds".to_sym) do
+ docstring "Measure #{name}"
+ base_labels Transaction::BASE_LABELS
+ buckets EXECUTION_MEASUREMENT_BUCKETS
+ end
- cpu_duration_seconds = fetch_histogram("gitlab_#{name}_cpu_duration_seconds".to_sym) do
- docstring "Measure #{name}"
- base_labels Transaction::BASE_LABELS
- buckets EXECUTION_MEASUREMENT_BUCKETS
- # with_feature "prometheus_metrics_measure_#{name}_cpu_duration"
- end
- cpu_duration_seconds.observe(trans.labels, cpu_time)
+ real_duration_seconds.observe(trans.labels, real_time)
- # InfluxDB stores the _real_time and _cpu_time time values as milliseconds
- trans.increment("#{name}_real_time", real_time.in_milliseconds, false)
- trans.increment("#{name}_cpu_time", cpu_time.in_milliseconds, false)
- trans.increment("#{name}_call_count", 1, false)
+ cpu_duration_seconds = fetch_histogram("gitlab_#{name}_cpu_duration_seconds".to_sym) do
+ docstring "Measure #{name}"
+ base_labels Transaction::BASE_LABELS
+ buckets EXECUTION_MEASUREMENT_BUCKETS
+ # with_feature "prometheus_metrics_measure_#{name}_cpu_duration"
+ end
+ cpu_duration_seconds.observe(trans.labels, cpu_time)
- retval
- end
+ # InfluxDB stores the _real_time and _cpu_time time values as milliseconds
+ trans.increment("#{name}_real_time", real_time.in_milliseconds, false)
+ trans.increment("#{name}_cpu_time", cpu_time.in_milliseconds, false)
+ trans.increment("#{name}_call_count", 1, false)
- # Sets the action of the current transaction (if any)
- #
- # action - The name of the action.
- def action=(action)
- trans = current_transaction
+ retval
+ end
- trans&.action = action
- end
+ # Sets the action of the current transaction (if any)
+ #
+ # action - The name of the action.
+ def action=(action)
+ trans = current_transaction
- # Tracks an event.
- #
- # See `Gitlab::Metrics::Transaction#add_event` for more details.
- def add_event(*args)
- trans = current_transaction
+ trans&.action = action
+ end
- trans&.add_event(*args)
- end
+ # Tracks an event.
+ #
+ # See `Gitlab::Metrics::Transaction#add_event` for more details.
+ def add_event(*args)
+ trans = current_transaction
- # Returns the prefix to use for the name of a series.
- def series_prefix
- @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
- end
+ trans&.add_event(*args)
+ end
- # Allow access from other metrics related middlewares
- def current_transaction
- Transaction.current
- end
+ # Returns the prefix to use for the name of a series.
+ def series_prefix
+ @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
+ end
+
+ # Allow access from other metrics related middlewares
+ def current_transaction
+ Transaction.current
+ end
- # When enabled this should be set before being used as the usual pattern
- # "@foo ||= bar" is _not_ thread-safe.
- # rubocop:disable Gitlab/ModuleWithInstanceVariables
- def pool
- if influx_metrics_enabled?
- if @pool.nil?
- MUTEX.synchronize do
- @pool ||= ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do
- host = settings[:host]
- port = settings[:port]
-
- InfluxDB::Client
- .new(udp: { host: host, port: port })
+ # When enabled this should be set before being used as the usual pattern
+ # "@foo ||= bar" is _not_ thread-safe.
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def pool
+ if influx_metrics_enabled?
+ if @pool.nil?
+ MUTEX.synchronize do
+ @pool ||= ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do
+ host = settings[:host]
+ port = settings[:port]
+
+ InfluxDB::Client
+ .new(udp: { host: host, port: port })
+ end
end
end
- end
- @pool
+ @pool
+ end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
- # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
end
end
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index 3fd8d0e1414..9c645ad6af4 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -4,13 +4,12 @@ module Gitlab
module Metrics
module Prometheus
extend ActiveSupport::Concern
- include Gitlab::Metrics::Concern
- include Gitlab::CurrentSettings
REGISTRY_MUTEX = Mutex.new
PROVIDER_MUTEX = Mutex.new
class_methods do
+ include Gitlab::CurrentSettings
include Gitlab::Utils::StrongMemoize
def metrics_folder_present?
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index 58e28592cf9..6795c1ab56b 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -144,7 +144,10 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
end
context 'with a transaction' do
+ let(:metric_cache_misses_total) { double('metric_cache_misses_total', increment: nil) }
+
before do
+ allow(subscriber).to receive(:metric_cache_misses_total).and_return(metric_cache_misses_total)
allow(subscriber).to receive(:current_transaction)
.and_return(transaction)
end
@@ -157,9 +160,9 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
end
it 'increments the cache_read_miss total' do
- expect(subscriber.send(:metric_cache_misses_total)).to receive(:increment).with({})
-
subscriber.cache_generate(event)
+
+ expect(metric_cache_misses_total).to have_received(:increment).with({})
end
end
end
--
cgit v1.2.1
From 938d9ffe40c7eca26703f25534c500339988acfb Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 11:55:36 +0100
Subject: Refactor metrics to use metrics dsl notation
---
lib/gitlab/metrics/method_call.rb | 11 ++++++-----
lib/gitlab/metrics/subscribers/action_view.rb | 11 ++++++-----
lib/gitlab/metrics/transaction.rb | 22 ++++++++++++----------
3 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index 7cb153237d4..b0a37b8a4eb 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -8,11 +8,12 @@ module Gitlab
BASE_LABELS = { module: nil, method: nil }.freeze
attr_reader :real_time, :cpu_time, :call_count, :labels
- define_histogram :gitlab_method_call_duration_seconds,
- docstring: 'Method calls real duration',
- base_labels: Transaction::BASE_LABELS.merge(BASE_LABELS),
- buckets: [0.01, 0.05, 0.1, 0.5, 1],
- with_feature: :prometheus_metrics_method_instrumentation
+ define_histogram :gitlab_method_call_duration_seconds do
+ docstring 'Method calls real duration'
+ base_labels Transaction::BASE_LABELS.merge(BASE_LABELS)
+ buckets [0.01, 0.05, 0.1, 0.5, 1]
+ with_feature :prometheus_metrics_method_instrumentation
+ end
# name - The full name of the method (including namespace) such as
# `User#sign_in`.
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index 2e3beb05294..86bd56ff7eb 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -4,11 +4,12 @@ module Gitlab
# Class for tracking the rendering timings of views.
class ActionView < ActiveSupport::Subscriber
include Gitlab::Metrics::Concern
- define_histogram :gitlab_view_rendering_duration_seconds,
- docstring: 'View rendering time',
- base_labels: Transaction::BASE_LABELS.merge({ path: nil }),
- buckets: [0.001, 0.01, 0.1, 10.0],
- with_feature: :prometheus_metrics_view_instrumentation
+ define_histogram :gitlab_view_rendering_duration_seconds do
+ docstring 'View rendering time'
+ base_labels Transaction::BASE_LABELS.merge({ path: nil })
+ buckets [0.001, 0.01, 0.1, 10.0]
+ with_feature :prometheus_metrics_view_instrumentation
+ end
attach_to :action_view
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index ed5a2cec935..c4b6c376e40 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -137,16 +137,18 @@ module Gitlab
"#{labels[:controller]}##{labels[:action]}" if labels && !labels.empty?
end
- define_histogram :gitlab_transaction_duration_seconds,
- docstring: 'Transaction duration',
- base_labels: BASE_LABELS,
- buckets: [0.001, 0.01, 0.1, 0.5, 10.0]
-
- define_histogram :gitlab_transaction_allocated_memory_bytes,
- docstring: 'Transaction allocated memory bytes',
- base_labels: BASE_LABELS,
- buckets: [100, 1000, 10000, 100000, 1000000, 10000000],
- with_feature: :prometheus_metrics_transaction_allocated_memory
+ define_histogram :gitlab_transaction_duration_seconds do
+ docstring 'Transaction duration'
+ base_labels BASE_LABELS
+ buckets [0.001, 0.01, 0.1, 0.5, 10.0]
+ end
+
+ define_histogram :gitlab_transaction_allocated_memory_bytes do
+ docstring 'Transaction allocated memory bytes'
+ base_labels BASE_LABELS
+ buckets [100, 1000, 10000, 100000, 1000000, 10000000]
+ with_feature :prometheus_metrics_transaction_allocated_memory
+ end
def self.transaction_metric(name, type, prefix: nil, tags: {})
metric_name = "gitlab_transaction_#{prefix}#{name}_total".to_sym
--
cgit v1.2.1
From 67d4946347d3784cc2bf3f12cad3b6fb2a6f09ff Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 12:12:21 +0100
Subject: Fix ruby sampler spec
---
.../gitlab/metrics/samplers/ruby_sampler_spec.rb | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 375cbf8a9ca..54781dd52fc 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -2,6 +2,11 @@ require 'spec_helper'
describe Gitlab::Metrics::Samplers::RubySampler do
let(:sampler) { described_class.new(5) }
+ let(:null_metric) { double('null_metric', set: nil, observe: nil) }
+
+ before do
+ allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric)
+ end
after do
Allocations.stop if Gitlab::Metrics.mri?
@@ -17,12 +22,9 @@ describe Gitlab::Metrics::Samplers::RubySampler do
end
it 'adds a metric containing the memory usage' do
- expect(Gitlab::Metrics::System).to receive(:memory_usage)
- .and_return(9000)
+ expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
- expect(sampler.metrics[:memory_usage]).to receive(:set)
- .with({}, 9000)
- .and_call_original
+ expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000)
sampler.sample
end
@@ -31,9 +33,7 @@ describe Gitlab::Metrics::Samplers::RubySampler do
expect(Gitlab::Metrics::System).to receive(:file_descriptor_count)
.and_return(4)
- expect(sampler.metrics[:file_descriptors]).to receive(:set)
- .with({}, 4)
- .and_call_original
+ expect(sampler.metrics[:file_descriptors]).to receive(:set).with({}, 4)
sampler.sample
end
@@ -49,16 +49,14 @@ describe Gitlab::Metrics::Samplers::RubySampler do
it 'adds a metric containing garbage collection time statistics' do
expect(GC::Profiler).to receive(:total_time).and_return(0.24)
- expect(sampler.metrics[:total_time]).to receive(:set)
- .with({}, 240)
- .and_call_original
+ expect(sampler.metrics[:total_time]).to receive(:set).with({}, 240)
sampler.sample
end
it 'adds a metric containing garbage collection statistics' do
GC.stat.keys.each do |key|
- expect(sampler.metrics[key]).to receive(:set).with({}, anything).and_call_original
+ expect(sampler.metrics[key]).to receive(:set).with({}, anything)
end
sampler.sample
--
cgit v1.2.1
From 1c403459ca1537fe105bdf18e31cc8f70d2ec2ed Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 12:47:16 +0100
Subject: Add missing feature gate for cpu metrics
---
lib/gitlab/metrics/influx_db.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 1fbe030681e..3789787d796 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -121,7 +121,7 @@ module Gitlab
docstring "Measure #{name}"
base_labels Transaction::BASE_LABELS
buckets EXECUTION_MEASUREMENT_BUCKETS
- # with_feature "prometheus_metrics_measure_#{name}_cpu_duration"
+ with_feature "prometheus_metrics_measure_#{name}_cpu_duration"
end
cpu_duration_seconds.observe(trans.labels, cpu_time)
--
cgit v1.2.1
From 05373c2490da3c1bad1a7b7f45ffd0fa11558c1c Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 14:18:34 +0100
Subject: Fix wrong histograam buckets
---
lib/gitlab/metrics/subscribers/action_view.rb | 2 +-
lib/gitlab/metrics/subscribers/active_record.rb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index 86bd56ff7eb..ce724040bc5 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -7,7 +7,7 @@ module Gitlab
define_histogram :gitlab_view_rendering_duration_seconds do
docstring 'View rendering time'
base_labels Transaction::BASE_LABELS.merge({ path: nil })
- buckets [0.001, 0.01, 0.1, 10.0]
+ buckets [0.001, 0.01, 0.1, 1, 10.0]
with_feature :prometheus_metrics_view_instrumentation
end
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index bdcbd873001..ff6542f6e00 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -20,7 +20,7 @@ module Gitlab
define_histogram :gitlab_sql_duration_seconds do
docstring 'SQL time'
base_labels Transaction::BASE_LABELS
- buckets [0.001, 0.01, 0.1, 10.0]
+ buckets [0.001, 0.01, 0.1, 1.0, 10.0]
end
def current_transaction
--
cgit v1.2.1
From 1ac96603e167a67ce8a69c17624625d3b2fd3400 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 16:43:04 +0100
Subject: Let mutex be initialized only once
---
lib/gitlab/metrics/concern.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index d9d69d8026e..30b06131190 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -6,7 +6,7 @@ module Gitlab
extend ActiveSupport::Concern
included do
- @@_metric_provider_mutex = Mutex.new
+ @@_metric_provider_mutex ||= Mutex.new
@_metrics_provider_cache = {}
end
--
cgit v1.2.1
From b1ba8eb9a20ee7fad6948799dc0b96ad34a0f0be Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 16:43:22 +0100
Subject: Do not use include Gitlab::CurrentSettings unnecessarily
---
lib/gitlab/metrics/influx_db.rb | 24 +++++++++++++-----------
lib/gitlab/metrics/prometheus.rb | 4 ++--
2 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 3789787d796..e640ddbc716 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -13,8 +13,6 @@ module Gitlab
private_constant :MUTEX
class_methods do
- include Gitlab::CurrentSettings
-
def influx_metrics_enabled?
settings[:enabled] || false
end
@@ -22,16 +20,20 @@ module Gitlab
# Prometheus histogram buckets used for arbitrary code measurements
def settings
- @settings ||= {
- enabled: current_application_settings[:metrics_enabled],
- pool_size: current_application_settings[:metrics_pool_size],
- timeout: current_application_settings[:metrics_timeout],
- method_call_threshold: current_application_settings[:metrics_method_call_threshold],
- host: current_application_settings[:metrics_host],
- port: current_application_settings[:metrics_port],
- sample_interval: current_application_settings[:metrics_sample_interval] || 15,
- packet_size: current_application_settings[:metrics_packet_size] || 1
+ @settings ||= begin
+ current_settings = Gitlab::CurrentSettings.current_application_settings
+
+ {
+ enabled: current_settings[:metrics_enabled],
+ pool_size: current_settings[:metrics_pool_size],
+ timeout: current_settings[:metrics_timeout],
+ method_call_threshold: current_settings[:metrics_method_call_threshold],
+ host: current_settings[:metrics_host],
+ port: current_settings[:metrics_port],
+ sample_interval: current_settings[:metrics_sample_interval] || 15,
+ packet_size: current_settings[:metrics_packet_size] || 1
}
+ end
end
def mri?
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index 9c645ad6af4..f07ea3560ff 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -9,7 +9,6 @@ module Gitlab
PROVIDER_MUTEX = Mutex.new
class_methods do
- include Gitlab::CurrentSettings
include Gitlab::Utils::StrongMemoize
def metrics_folder_present?
@@ -72,7 +71,8 @@ module Gitlab
end
def prometheus_metrics_enabled_unmemoized
- metrics_folder_present? && current_application_settings[:prometheus_metrics_enabled] || false
+ metrics_folder_present? &&
+ Gitlab::CurrentSettings.current_application_settings[:prometheus_metrics_enabled] || false
end
end
end
--
cgit v1.2.1
From a760445f9bd2c7cba854bbb55da2202a9f5d4de5 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 16:43:29 +0100
Subject: fix typo in the buckets
---
lib/gitlab/metrics/transaction.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index c4b6c376e40..5a1f6de92ee 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -140,7 +140,7 @@ module Gitlab
define_histogram :gitlab_transaction_duration_seconds do
docstring 'Transaction duration'
base_labels BASE_LABELS
- buckets [0.001, 0.01, 0.1, 0.5, 10.0]
+ buckets [0.001, 0.01, 0.1, 1.0, 10.0]
end
define_histogram :gitlab_transaction_allocated_memory_bytes do
--
cgit v1.2.1
From f51147834ee49874f0e3f311d30af622b1623f95 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 16:44:13 +0100
Subject: use define_method to define devine_metric_method. (couldn't resist)
---
spec/lib/gitlab/metrics/concern_spec.rb | 27 ++++++++++++---------------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
index cf2a508fe04..c0d4ff8dd0f 100644
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -8,17 +8,15 @@ describe Gitlab::Metrics::Concern do
let(:metric_name) { :sample_metric }
describe "#define_#{metric_type}" do
- let(:_metric_type) { metric_type }
-
- def define_metric_method(**args)
- subject.send("define_#{_metric_type}", metric_name, **args)
+ define_method(:call_define_metric_method) do |**args|
+ subject.__send__("define_#{metric_type}", metric_name, **args)
end
context 'metrics access method not defined' do
it "defines metrics accessing method" do
expect(subject).not_to respond_to(metric_name)
- define_metric_method(docstring: docstring)
+ call_define_metric_method(docstring: docstring)
expect(subject).to respond_to(metric_name)
end
@@ -26,11 +24,11 @@ describe Gitlab::Metrics::Concern do
context 'metrics access method defined' do
before do
- define_metric_method(docstring: docstring)
+ call_define_metric_method(docstring: docstring)
end
it 'raises error when trying to redefine method' do
- expect { define_metric_method(docstring: docstring) }.to raise_error(ArgumentError)
+ expect { call_define_metric_method(docstring: docstring) }.to raise_error(ArgumentError)
end
context 'metric is not cached' do
@@ -56,18 +54,17 @@ describe Gitlab::Metrics::Concern do
end
describe "#fetch_#{metric_type}" do
- let(:_metric_type) { metric_type }
let(:null_metric) { Gitlab::Metrics::NullMetric.instance }
- def fetch_metric_method(**args)
- subject.send("fetch_#{_metric_type}", metric_name, **args)
+ define_method(:call_fetch_metric_method) do |**args|
+ subject.__send__("fetch_#{metric_type}", metric_name, **args)
end
context "when #{metric_type} is not cached" do
it 'initializes counter metric' do
allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
- fetch_metric_method(docstring: docstring)
+ call_fetch_metric_method(docstring: docstring)
expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
end
@@ -75,13 +72,13 @@ describe Gitlab::Metrics::Concern do
context "when #{metric_type} is cached" do
before do
- fetch_metric_method(docstring: docstring)
+ call_fetch_metric_method(docstring: docstring)
end
it 'uses class metric cache' do
expect(Gitlab::Metrics).not_to receive(metric_type)
- fetch_metric_method(docstring: docstring)
+ call_fetch_metric_method(docstring: docstring)
end
context 'when metric is reloaded' do
@@ -92,7 +89,7 @@ describe Gitlab::Metrics::Concern do
it "initializes #{metric_type} metric" do
allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
- fetch_metric_method(docstring: docstring)
+ call_fetch_metric_method(docstring: docstring)
expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
end
@@ -101,7 +98,7 @@ describe Gitlab::Metrics::Concern do
context 'when metric is configured with feature' do
let(:feature_name) { :some_metric_feature }
- let(:metric) { fetch_metric_method(docstring: docstring, with_feature: feature_name) }
+ let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) }
context 'when feature is enabled' do
before do
--
cgit v1.2.1
From 7772dfb788cead286265b491c0ce28dbcf5f9fd2 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 16:44:29 +0100
Subject: before -> after
---
spec/lib/gitlab/metrics/method_call_spec.rb | 2 +-
spec/lib/gitlab/metrics_spec.rb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index e3e594a75e4..d9379cfe674 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Metrics::MethodCall do
let(:method_call) { described_class.new('Foo#bar', :Foo, '#bar', transaction) }
describe '#measure' do
- before do
+ after do
described_class.reload_metric!(:gitlab_method_call_duration_seconds)
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 1619fbd88b1..9e405e9f736 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Metrics do
context 'prometheus metrics enabled in config' do
before do
- allow(described_class).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true)
+ allow(Gitlab::CurrentSettings).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true)
end
context 'when metrics folder is present' do
--
cgit v1.2.1
From ffac08d290bfcc7459a1c9e58c44efc8c7be6a0e Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 20:52:41 +0100
Subject: Use class variables for metrics provider cache
---
lib/gitlab/metrics/concern.rb | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 30b06131190..924b53a615e 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -7,12 +7,12 @@ module Gitlab
included do
@@_metric_provider_mutex ||= Mutex.new
- @_metrics_provider_cache = {}
+ @@_metrics_provider_cache = {}
end
class_methods do
def reload_metric!(name)
- @_metrics_provider_cache.delete(name)
+ @@_metrics_provider_cache.delete(name)
end
private
@@ -23,12 +23,12 @@ module Gitlab
end
define_singleton_method(name) do
- @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
+ @@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
end
end
def fetch_metric(type, name, opts = {}, &block)
- @_metrics_provider_cache&.[](name) || init_metric(type, name, opts, &block)
+ @@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
end
def init_metric(type, name, opts = {}, &block)
@@ -44,8 +44,7 @@ module Gitlab
def synchronized_cache_fill(key)
@@_metric_provider_mutex.synchronize do
- @_metrics_provider_cache ||= {}
- @_metrics_provider_cache[key] ||= yield
+ @@_metrics_provider_cache[key] ||= yield
end
end
--
cgit v1.2.1
From 75b49298f70edfd5367e40d06c28e8a3e4688564 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 20:53:02 +0100
Subject: remove unused (?) variables
---
lib/gitlab/metrics/influx_db.rb | 3 ---
1 file changed, 3 deletions(-)
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index e640ddbc716..d64a60245db 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -5,9 +5,6 @@ module Gitlab
include Gitlab::Metrics::Concern
EXECUTION_MEASUREMENT_BUCKETS = [0.001, 0.01, 0.1, 1].freeze
- RAILS_ROOT = Rails.root.to_s
- METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
- PATH_REGEX = /^#{RAILS_ROOT}\/?/
MUTEX = Mutex.new
private_constant :MUTEX
--
cgit v1.2.1
From 2b5a639d794f42156c224ccef50910c4cf44b791 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 20:53:14 +0100
Subject: use public_send where appropriate
---
spec/lib/gitlab/metrics/concern_spec.rb | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
index c0d4ff8dd0f..a170c86cdc1 100644
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ b/spec/lib/gitlab/metrics/concern_spec.rb
@@ -35,19 +35,19 @@ describe Gitlab::Metrics::Concern do
it 'calls fetch_metric' do
expect(subject).to receive(:init_metric).with(metric_type, metric_name, docstring: docstring)
- subject.send(metric_name)
+ subject.public_send(metric_name)
end
end
context 'metric is cached' do
before do
- subject.send(metric_name)
+ subject.public_send(metric_name)
end
it 'returns cached metric' do
expect(subject).not_to receive(:init_metric)
- subject.send(metric_name)
+ subject.public_send(metric_name)
end
end
end
--
cgit v1.2.1
From 098cf7d257b5767625ed052b2f442cb216261daa Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 22 Jan 2018 20:55:48 +0100
Subject: add comment aabout hotspots
---
lib/gitlab/metrics/concern.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
index 924b53a615e..35119611a65 100644
--- a/lib/gitlab/metrics/concern.rb
+++ b/lib/gitlab/metrics/concern.rb
@@ -23,6 +23,7 @@ module Gitlab
end
define_singleton_method(name) do
+ # inlining fetch_metric method to avoid method call overhead when instrumenting hot spots
@@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
end
end
--
cgit v1.2.1
From d4c768ce09daac5f6bcfebae2313dc943961b356 Mon Sep 17 00:00:00 2001
From: Pawel Chojnacki
Date: Mon, 29 Jan 2018 12:33:08 +0100
Subject: Rename Concern -> Methods
---
lib/gitlab/gitaly_client.rb | 2 +-
lib/gitlab/metrics/concern.rb | 129 ----------------------
lib/gitlab/metrics/concern/metric_options.rb | 61 -----------
lib/gitlab/metrics/influx_db.rb | 2 +-
lib/gitlab/metrics/method_call.rb | 2 +-
lib/gitlab/metrics/methods.rb | 129 ++++++++++++++++++++++
lib/gitlab/metrics/methods/metric_options.rb | 61 +++++++++++
lib/gitlab/metrics/subscribers/action_view.rb | 2 +-
lib/gitlab/metrics/subscribers/active_record.rb | 2 +-
lib/gitlab/metrics/transaction.rb | 2 +-
spec/lib/gitlab/metrics/concern_spec.rb | 137 ------------------------
spec/lib/gitlab/metrics/methods_spec.rb | 137 ++++++++++++++++++++++++
12 files changed, 333 insertions(+), 333 deletions(-)
delete mode 100644 lib/gitlab/metrics/concern.rb
delete mode 100644 lib/gitlab/metrics/concern/metric_options.rb
create mode 100644 lib/gitlab/metrics/methods.rb
create mode 100644 lib/gitlab/metrics/methods/metric_options.rb
delete mode 100644 spec/lib/gitlab/metrics/concern_spec.rb
create mode 100644 spec/lib/gitlab/metrics/methods_spec.rb
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 583ef1c19e1..c5d3e944f7d 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -6,7 +6,7 @@ require 'grpc/health/v1/health_services_pb'
module Gitlab
module GitalyClient
- include Gitlab::Metrics::Concern
+ include Gitlab::Metrics::Methods
module MigrationStatus
DISABLED = 1
OPT_IN = 2
diff --git a/lib/gitlab/metrics/concern.rb b/lib/gitlab/metrics/concern.rb
deleted file mode 100644
index 35119611a65..00000000000
--- a/lib/gitlab/metrics/concern.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-# rubocop:disable Style/ClassVars
-
-module Gitlab
- module Metrics
- module Concern
- extend ActiveSupport::Concern
-
- included do
- @@_metric_provider_mutex ||= Mutex.new
- @@_metrics_provider_cache = {}
- end
-
- class_methods do
- def reload_metric!(name)
- @@_metrics_provider_cache.delete(name)
- end
-
- private
-
- def define_metric(type, name, opts = {}, &block)
- if respond_to?(name)
- raise ArgumentError, "method #{name} already exists"
- end
-
- define_singleton_method(name) do
- # inlining fetch_metric method to avoid method call overhead when instrumenting hot spots
- @@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
- end
- end
-
- def fetch_metric(type, name, opts = {}, &block)
- @@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
- end
-
- def init_metric(type, name, opts = {}, &block)
- options = MetricOptions.new(opts)
- options.evaluate(&block)
-
- if disabled_by_feature(options)
- synchronized_cache_fill(name) { NullMetric.instance }
- else
- synchronized_cache_fill(name) { build_metric!(type, name, options) }
- end
- end
-
- def synchronized_cache_fill(key)
- @@_metric_provider_mutex.synchronize do
- @@_metrics_provider_cache[key] ||= yield
- end
- end
-
- def disabled_by_feature(options)
- options.with_feature && !Feature.get(options.with_feature).enabled?
- end
-
- def build_metric!(type, name, options)
- case type
- when :gauge
- Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
- when :counter
- Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
- when :histogram
- Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
- when :summary
- raise NotImplementedError, "summary metrics are not currently supported"
- else
- raise ArgumentError, "uknown metric type #{type}"
- end
- end
-
- # Fetch and/or initialize counter metric
- # @param [Symbol] name
- # @param [Hash] opts
- def fetch_counter(name, opts = {}, &block)
- fetch_metric(:counter, name, opts, &block)
- end
-
- # Fetch and/or initialize gauge metric
- # @param [Symbol] name
- # @param [Hash] opts
- def fetch_gauge(name, opts = {}, &block)
- fetch_metric(:gauge, name, opts, &block)
- end
-
- # Fetch and/or initialize histogram metric
- # @param [Symbol] name
- # @param [Hash] opts
- def fetch_histogram(name, opts = {}, &block)
- fetch_metric(:histogram, name, opts, &block)
- end
-
- # Fetch and/or initialize summary metric
- # @param [Symbol] name
- # @param [Hash] opts
- def fetch_summary(name, opts = {}, &block)
- fetch_metric(:summary, name, opts, &block)
- end
-
- # Define metric accessor method for a Counter
- # @param [Symbol] name
- # @param [Hash] opts
- def define_counter(name, opts = {}, &block)
- define_metric(:counter, name, opts, &block)
- end
-
- # Define metric accessor method for a Gauge
- # @param [Symbol] name
- # @param [Hash] opts
- def define_gauge(name, opts = {}, &block)
- define_metric(:gauge, name, opts, &block)
- end
-
- # Define metric accessor method for a Histogram
- # @param [Symbol] name
- # @param [Hash] opts
- def define_histogram(name, opts = {}, &block)
- define_metric(:histogram, name, opts, &block)
- end
-
- # Define metric accessor method for a Summary
- # @param [Symbol] name
- # @param [Hash] opts
- def define_summary(name, opts = {}, &block)
- define_metric(:summary, name, opts, &block)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/concern/metric_options.rb b/lib/gitlab/metrics/concern/metric_options.rb
deleted file mode 100644
index 1f860b9b77f..00000000000
--- a/lib/gitlab/metrics/concern/metric_options.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-module Gitlab
- module Metrics
- module Concern
- class MetricOptions
- SMALL_NETWORK_BUCKETS = [0.005, 0.01, 0.1, 1, 10].freeze
-
- def initialize(options = {})
- @multiprocess_mode = options[:multiprocess_mode] || :all
- @buckets = options[:buckets] || SMALL_NETWORK_BUCKETS
- @base_labels = options[:base_labels] || {}
- @docstring = options[:docstring]
- @with_feature = options[:with_feature]
- end
-
- # Documentation describing metric in metrics endpoint '/-/metrics'
- def docstring(docstring = nil)
- @docstring = docstring unless docstring.nil?
-
- @docstring
- end
-
- # Gauge aggregation mode for multiprocess metrics
- # - :all (default) returns each gauge for every process
- # - :livesum all process'es gauges summed up
- # - :max maximum value of per process gauges
- # - :min minimum value of per process gauges
- def multiprocess_mode(mode = nil)
- @multiprocess_mode = mode unless mode.nil?
-
- @multiprocess_mode
- end
-
- # Measurement buckets for histograms
- def buckets(buckets = nil)
- @buckets = buckets unless buckets.nil?
-
- @buckets
- end
-
- # Base labels are merged with per metric labels
- def base_labels(base_labels = nil)
- @base_labels = base_labels unless base_labels.nil?
-
- @base_labels
- end
-
- # Use feature toggle to control whether certain metric is enabled/disabled
- def with_feature(name = nil)
- @with_feature = name unless name.nil?
-
- @with_feature
- end
-
- def evaluate(&block)
- instance_eval(&block) if block_given?
- self
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index d64a60245db..66f30e3b397 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -2,7 +2,7 @@ module Gitlab
module Metrics
module InfluxDb
extend ActiveSupport::Concern
- include Gitlab::Metrics::Concern
+ include Gitlab::Metrics::Methods
EXECUTION_MEASUREMENT_BUCKETS = [0.001, 0.01, 0.1, 1].freeze
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index b0a37b8a4eb..b11520a79bb 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -4,7 +4,7 @@ module Gitlab
module Metrics
# Class for tracking timing information about method calls
class MethodCall
- include Gitlab::Metrics::Concern
+ include Gitlab::Metrics::Methods
BASE_LABELS = { module: nil, method: nil }.freeze
attr_reader :real_time, :cpu_time, :call_count, :labels
diff --git a/lib/gitlab/metrics/methods.rb b/lib/gitlab/metrics/methods.rb
new file mode 100644
index 00000000000..cd7c1e507f7
--- /dev/null
+++ b/lib/gitlab/metrics/methods.rb
@@ -0,0 +1,129 @@
+# rubocop:disable Style/ClassVars
+
+module Gitlab
+ module Metrics
+ module Methods
+ extend ActiveSupport::Concern
+
+ included do
+ @@_metric_provider_mutex ||= Mutex.new
+ @@_metrics_provider_cache = {}
+ end
+
+ class_methods do
+ def reload_metric!(name)
+ @@_metrics_provider_cache.delete(name)
+ end
+
+ private
+
+ def define_metric(type, name, opts = {}, &block)
+ if respond_to?(name)
+ raise ArgumentError, "method #{name} already exists"
+ end
+
+ define_singleton_method(name) do
+ # inlining fetch_metric method to avoid method call overhead when instrumenting hot spots
+ @@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
+ end
+ end
+
+ def fetch_metric(type, name, opts = {}, &block)
+ @@_metrics_provider_cache[name] || init_metric(type, name, opts, &block)
+ end
+
+ def init_metric(type, name, opts = {}, &block)
+ options = MetricOptions.new(opts)
+ options.evaluate(&block)
+
+ if disabled_by_feature(options)
+ synchronized_cache_fill(name) { NullMetric.instance }
+ else
+ synchronized_cache_fill(name) { build_metric!(type, name, options) }
+ end
+ end
+
+ def synchronized_cache_fill(key)
+ @@_metric_provider_mutex.synchronize do
+ @@_metrics_provider_cache[key] ||= yield
+ end
+ end
+
+ def disabled_by_feature(options)
+ options.with_feature && !Feature.get(options.with_feature).enabled?
+ end
+
+ def build_metric!(type, name, options)
+ case type
+ when :gauge
+ Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
+ when :counter
+ Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
+ when :histogram
+ Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
+ when :summary
+ raise NotImplementedError, "summary metrics are not currently supported"
+ else
+ raise ArgumentError, "uknown metric type #{type}"
+ end
+ end
+
+ # Fetch and/or initialize counter metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_counter(name, opts = {}, &block)
+ fetch_metric(:counter, name, opts, &block)
+ end
+
+ # Fetch and/or initialize gauge metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_gauge(name, opts = {}, &block)
+ fetch_metric(:gauge, name, opts, &block)
+ end
+
+ # Fetch and/or initialize histogram metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_histogram(name, opts = {}, &block)
+ fetch_metric(:histogram, name, opts, &block)
+ end
+
+ # Fetch and/or initialize summary metric
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def fetch_summary(name, opts = {}, &block)
+ fetch_metric(:summary, name, opts, &block)
+ end
+
+ # Define metric accessor method for a Counter
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def define_counter(name, opts = {}, &block)
+ define_metric(:counter, name, opts, &block)
+ end
+
+ # Define metric accessor method for a Gauge
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def define_gauge(name, opts = {}, &block)
+ define_metric(:gauge, name, opts, &block)
+ end
+
+ # Define metric accessor method for a Histogram
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def define_histogram(name, opts = {}, &block)
+ define_metric(:histogram, name, opts, &block)
+ end
+
+ # Define metric accessor method for a Summary
+ # @param [Symbol] name
+ # @param [Hash] opts
+ def define_summary(name, opts = {}, &block)
+ define_metric(:summary, name, opts, &block)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/methods/metric_options.rb b/lib/gitlab/metrics/methods/metric_options.rb
new file mode 100644
index 00000000000..70e122d4e15
--- /dev/null
+++ b/lib/gitlab/metrics/methods/metric_options.rb
@@ -0,0 +1,61 @@
+module Gitlab
+ module Metrics
+ module Methods
+ class MetricOptions
+ SMALL_NETWORK_BUCKETS = [0.005, 0.01, 0.1, 1, 10].freeze
+
+ def initialize(options = {})
+ @multiprocess_mode = options[:multiprocess_mode] || :all
+ @buckets = options[:buckets] || SMALL_NETWORK_BUCKETS
+ @base_labels = options[:base_labels] || {}
+ @docstring = options[:docstring]
+ @with_feature = options[:with_feature]
+ end
+
+ # Documentation describing metric in metrics endpoint '/-/metrics'
+ def docstring(docstring = nil)
+ @docstring = docstring unless docstring.nil?
+
+ @docstring
+ end
+
+ # Gauge aggregation mode for multiprocess metrics
+ # - :all (default) returns each gauge for every process
+ # - :livesum all process'es gauges summed up
+ # - :max maximum value of per process gauges
+ # - :min minimum value of per process gauges
+ def multiprocess_mode(mode = nil)
+ @multiprocess_mode = mode unless mode.nil?
+
+ @multiprocess_mode
+ end
+
+ # Measurement buckets for histograms
+ def buckets(buckets = nil)
+ @buckets = buckets unless buckets.nil?
+
+ @buckets
+ end
+
+ # Base labels are merged with per metric labels
+ def base_labels(base_labels = nil)
+ @base_labels = base_labels unless base_labels.nil?
+
+ @base_labels
+ end
+
+ # Use feature toggle to control whether certain metric is enabled/disabled
+ def with_feature(name = nil)
+ @with_feature = name unless name.nil?
+
+ @with_feature
+ end
+
+ def evaluate(&block)
+ instance_eval(&block) if block_given?
+ self
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index ce724040bc5..274436ca2b4 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -3,7 +3,7 @@ module Gitlab
module Subscribers
# Class for tracking the rendering timings of views.
class ActionView < ActiveSupport::Subscriber
- include Gitlab::Metrics::Concern
+ include Gitlab::Metrics::Methods
define_histogram :gitlab_view_rendering_duration_seconds do
docstring 'View rendering time'
base_labels Transaction::BASE_LABELS.merge({ path: nil })
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index ff6542f6e00..4b3e8d0a6a0 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -3,7 +3,7 @@ module Gitlab
module Subscribers
# Class for tracking the total query duration of a transaction.
class ActiveRecord < ActiveSupport::Subscriber
- include Gitlab::Metrics::Concern
+ include Gitlab::Metrics::Methods
attach_to :active_record
def sql(event)
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 5a1f6de92ee..45b9e14ba55 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -2,7 +2,7 @@ module Gitlab
module Metrics
# Class for storing metrics information of a single transaction.
class Transaction
- include Gitlab::Metrics::Concern
+ include Gitlab::Metrics::Methods
# base labels shared among all transactions
BASE_LABELS = { controller: nil, action: nil }.freeze
diff --git a/spec/lib/gitlab/metrics/concern_spec.rb b/spec/lib/gitlab/metrics/concern_spec.rb
deleted file mode 100644
index a170c86cdc1..00000000000
--- a/spec/lib/gitlab/metrics/concern_spec.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Metrics::Concern do
- subject { Class.new { include Gitlab::Metrics::Concern } }
-
- shared_context 'metric' do |metric_type, *args|
- let(:docstring) { 'description' }
- let(:metric_name) { :sample_metric }
-
- describe "#define_#{metric_type}" do
- define_method(:call_define_metric_method) do |**args|
- subject.__send__("define_#{metric_type}", metric_name, **args)
- end
-
- context 'metrics access method not defined' do
- it "defines metrics accessing method" do
- expect(subject).not_to respond_to(metric_name)
-
- call_define_metric_method(docstring: docstring)
-
- expect(subject).to respond_to(metric_name)
- end
- end
-
- context 'metrics access method defined' do
- before do
- call_define_metric_method(docstring: docstring)
- end
-
- it 'raises error when trying to redefine method' do
- expect { call_define_metric_method(docstring: docstring) }.to raise_error(ArgumentError)
- end
-
- context 'metric is not cached' do
- it 'calls fetch_metric' do
- expect(subject).to receive(:init_metric).with(metric_type, metric_name, docstring: docstring)
-
- subject.public_send(metric_name)
- end
- end
-
- context 'metric is cached' do
- before do
- subject.public_send(metric_name)
- end
-
- it 'returns cached metric' do
- expect(subject).not_to receive(:init_metric)
-
- subject.public_send(metric_name)
- end
- end
- end
- end
-
- describe "#fetch_#{metric_type}" do
- let(:null_metric) { Gitlab::Metrics::NullMetric.instance }
-
- define_method(:call_fetch_metric_method) do |**args|
- subject.__send__("fetch_#{metric_type}", metric_name, **args)
- end
-
- context "when #{metric_type} is not cached" do
- it 'initializes counter metric' do
- allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
-
- call_fetch_metric_method(docstring: docstring)
-
- expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
- end
- end
-
- context "when #{metric_type} is cached" do
- before do
- call_fetch_metric_method(docstring: docstring)
- end
-
- it 'uses class metric cache' do
- expect(Gitlab::Metrics).not_to receive(metric_type)
-
- call_fetch_metric_method(docstring: docstring)
- end
-
- context 'when metric is reloaded' do
- before do
- subject.reload_metric!(metric_name)
- end
-
- it "initializes #{metric_type} metric" do
- allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
-
- call_fetch_metric_method(docstring: docstring)
-
- expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
- end
- end
- end
-
- context 'when metric is configured with feature' do
- let(:feature_name) { :some_metric_feature }
- let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) }
-
- context 'when feature is enabled' do
- before do
- Feature.get(feature_name).enable
- end
-
- it "initializes #{metric_type} metric" do
- allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
-
- metric
-
- expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
- end
- end
-
- context 'when feature is disabled' do
- before do
- Feature.get(feature_name).disable
- end
-
- it "returns NullMetric" do
- allow(Gitlab::Metrics).to receive(metric_type)
-
- expect(metric).to be_instance_of(Gitlab::Metrics::NullMetric)
-
- expect(Gitlab::Metrics).not_to have_received(metric_type)
- end
- end
- end
- end
- end
-
- include_examples 'metric', :counter, {}
- include_examples 'metric', :gauge, {}, :all
- include_examples 'metric', :histogram, {}, [0.005, 0.01, 0.1, 1, 10]
-end
diff --git a/spec/lib/gitlab/metrics/methods_spec.rb b/spec/lib/gitlab/metrics/methods_spec.rb
new file mode 100644
index 00000000000..9d41ed2442b
--- /dev/null
+++ b/spec/lib/gitlab/metrics/methods_spec.rb
@@ -0,0 +1,137 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Methods do
+ subject { Class.new { include Gitlab::Metrics::Methods } }
+
+ shared_context 'metric' do |metric_type, *args|
+ let(:docstring) { 'description' }
+ let(:metric_name) { :sample_metric }
+
+ describe "#define_#{metric_type}" do
+ define_method(:call_define_metric_method) do |**args|
+ subject.__send__("define_#{metric_type}", metric_name, **args)
+ end
+
+ context 'metrics access method not defined' do
+ it "defines metrics accessing method" do
+ expect(subject).not_to respond_to(metric_name)
+
+ call_define_metric_method(docstring: docstring)
+
+ expect(subject).to respond_to(metric_name)
+ end
+ end
+
+ context 'metrics access method defined' do
+ before do
+ call_define_metric_method(docstring: docstring)
+ end
+
+ it 'raises error when trying to redefine method' do
+ expect { call_define_metric_method(docstring: docstring) }.to raise_error(ArgumentError)
+ end
+
+ context 'metric is not cached' do
+ it 'calls fetch_metric' do
+ expect(subject).to receive(:init_metric).with(metric_type, metric_name, docstring: docstring)
+
+ subject.public_send(metric_name)
+ end
+ end
+
+ context 'metric is cached' do
+ before do
+ subject.public_send(metric_name)
+ end
+
+ it 'returns cached metric' do
+ expect(subject).not_to receive(:init_metric)
+
+ subject.public_send(metric_name)
+ end
+ end
+ end
+ end
+
+ describe "#fetch_#{metric_type}" do
+ let(:null_metric) { Gitlab::Metrics::NullMetric.instance }
+
+ define_method(:call_fetch_metric_method) do |**args|
+ subject.__send__("fetch_#{metric_type}", metric_name, **args)
+ end
+
+ context "when #{metric_type} is not cached" do
+ it 'initializes counter metric' do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ call_fetch_metric_method(docstring: docstring)
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+
+ context "when #{metric_type} is cached" do
+ before do
+ call_fetch_metric_method(docstring: docstring)
+ end
+
+ it 'uses class metric cache' do
+ expect(Gitlab::Metrics).not_to receive(metric_type)
+
+ call_fetch_metric_method(docstring: docstring)
+ end
+
+ context 'when metric is reloaded' do
+ before do
+ subject.reload_metric!(metric_name)
+ end
+
+ it "initializes #{metric_type} metric" do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ call_fetch_metric_method(docstring: docstring)
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+ end
+
+ context 'when metric is configured with feature' do
+ let(:feature_name) { :some_metric_feature }
+ let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) }
+
+ context 'when feature is enabled' do
+ before do
+ Feature.get(feature_name).enable
+ end
+
+ it "initializes #{metric_type} metric" do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ metric
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ Feature.get(feature_name).disable
+ end
+
+ it "returns NullMetric" do
+ allow(Gitlab::Metrics).to receive(metric_type)
+
+ expect(metric).to be_instance_of(Gitlab::Metrics::NullMetric)
+
+ expect(Gitlab::Metrics).not_to have_received(metric_type)
+ end
+ end
+ end
+ end
+ end
+
+ include_examples 'metric', :counter, {}
+ include_examples 'metric', :gauge, {}, :all
+ include_examples 'metric', :histogram, {}, [0.005, 0.01, 0.1, 1, 10]
+end
--
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 d90d141c24228b8df6333b03d26a1723480837ba Mon Sep 17 00:00:00 2001
From: Tony Rom
Date: Wed, 20 Dec 2017 14:39:40 +0300
Subject: Add Colors to GitLab Flavored Markdown
---
app/assets/javascripts/gfm_auto_complete.js | 2 +-
app/assets/stylesheets/framework/gfm.scss | 30 ++++++++
changelogs/unreleased/24167__color_label.yml | 5 ++
doc/user/markdown.md | 41 ++++++++++-
lib/banzai/color_parser.rb | 50 +++++++++++++
lib/banzai/filter/color_filter.rb | 31 ++++++++
lib/banzai/pipeline/broadcast_message_pipeline.rb | 1 +
lib/banzai/pipeline/gfm_pipeline.rb | 1 +
spec/features/markdown_spec.rb | 8 ++
spec/fixtures/markdown.md.erb | 12 +++
spec/javascripts/gfm_auto_complete_spec.js | 9 +++
spec/lib/banzai/color_parser_spec.rb | 90 +++++++++++++++++++++++
spec/lib/banzai/filter/color_filter_spec.rb | 61 +++++++++++++++
spec/support/matchers/markdown_matchers.rb | 21 ++++++
14 files changed, 360 insertions(+), 2 deletions(-)
create mode 100644 changelogs/unreleased/24167__color_label.yml
create mode 100644 lib/banzai/color_parser.rb
create mode 100644 lib/banzai/filter/color_filter.rb
create mode 100644 spec/lib/banzai/color_parser_spec.rb
create mode 100644 spec/lib/banzai/filter/color_filter_spec.rb
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index df20e1e9c88..57a1fa107e5 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -461,7 +461,7 @@ class GfmAutoComplete {
const accentAChar = decodeURI('%C3%80');
const accentYChar = decodeURI('%C3%BF');
- const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
+ const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
return regexp.exec(targetSubtext);
}
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 5621505996d..6eff57157cc 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -16,3 +16,33 @@
background-color: $user-mention-bg-hover;
}
}
+
+.gfm-color_chip {
+ display: inline-block;
+ margin-left: 4px;
+ margin-bottom: 2px;
+ vertical-align: middle;
+ border-radius: 3px;
+
+ $side: 0.9em;
+ $bg-size: $side / 0.9;
+ $bg-pos: $bg-size / 2;
+ $bg-color: $gray-dark;
+
+ width: $side;
+ height: $side;
+ background: $white-light;
+ background-image: linear-gradient(135deg, $bg-color 25%, transparent 0%, transparent 75%, $bg-color 0%),
+ linear-gradient(135deg, $bg-color 25%, transparent 0%, transparent 75%, $bg-color 0%);
+ background-size: $bg-size $bg-size;
+ background-position: 0 0, $bg-pos $bg-pos;
+
+ > span {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ margin-bottom: 2px;
+ border-radius: 3px;
+ border: 1px solid $black-transparent;
+ }
+}
diff --git a/changelogs/unreleased/24167__color_label.yml b/changelogs/unreleased/24167__color_label.yml
new file mode 100644
index 00000000000..68c6c731163
--- /dev/null
+++ b/changelogs/unreleased/24167__color_label.yml
@@ -0,0 +1,5 @@
+---
+title: Add Colors to GitLab Flavored Markdown
+merge_request: 16095
+author: Tony Rom
+type: added
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 552abac747b..b590dfa0d40 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -253,7 +253,7 @@ GFM will recognize the following:
| `@user_name` | specific user |
| `@group_name` | specific group |
| `@all` | entire team |
-| `#123` | issue |
+| `#12345` | issue |
| `!123` | merge request |
| `$123` | snippet |
| `~123` | label by ID |
@@ -379,6 +379,45 @@ _Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._
>**Note:**
This also works for the asciidoctor `:stem: latexmath`. For details see the [asciidoctor user manual][asciidoctor-manual].
+### Colors
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#colors
+
+It is possible to have color written in HEX, RGB or HSL format rendered with a color indicator.
+
+Color written inside backticks will be followed by a color "chip".
+
+Examples:
+
+ `#F00`
+ `#F00A`
+ `#FF0000`
+ `#FF0000AA`
+ `RGB(0,255,0)`
+ `RGB(0%,100%,0%)`
+ `RGBA(0,255,0,0.7)`
+ `HSL(540,70%,50%)`
+ `HSLA(540,70%,50%,0.7)`
+
+Becomes:
+
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.7)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.7)`
+
+#### Supported formats:
+
+* HEX: `` `#RGB[A]` `` or `` `#RRGGBB[AA]` ``
+* RGB: `` `RGB[A](R, G, B[, A])` ``
+* HSL: `` `HSL[A](H, S, L[, A])` ``
+
### Mermaid
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in
diff --git a/lib/banzai/color_parser.rb b/lib/banzai/color_parser.rb
new file mode 100644
index 00000000000..d96c0a1ed1f
--- /dev/null
+++ b/lib/banzai/color_parser.rb
@@ -0,0 +1,50 @@
+module Banzai
+ module ColorParser
+ ALPHA = /0(?:\.\d+)?|\.\d+|1(?:\.0+)?/ # 0.0..1.0
+ PERCENTS = /(?:\d{1,2}|100)%/ # 00%..100%
+ ALPHA_CHANNEL = /(?:,\s*(?:#{ALPHA}|#{PERCENTS}))?/
+ BITS = /\d{1,2}|1\d\d|2(?:[0-4]\d|5[0-5])/ # 00..255
+ DEGS = /-?\d+(?:deg)?/i # [-]digits[deg]
+ RADS = /-?(?:\d+(?:\.\d+)?|\.\d+)rad/i # [-](digits[.digits] OR .digits)rad
+ HEX_FORMAT = /\#(?:\h{3}|\h{4}|\h{6}|\h{8})/
+ RGB_FORMAT = /
+ (?:rgba?
+ \(
+ (?:
+ (?:(?:#{BITS},\s*){2}#{BITS})
+ |
+ (?:(?:#{PERCENTS},\s*){2}#{PERCENTS})
+ )
+ #{ALPHA_CHANNEL}
+ \)
+ )
+ /xi
+ HSL_FORMAT = /
+ (?:hsla?
+ \(
+ (?:#{DEGS}|#{RADS}),\s*#{PERCENTS},\s*#{PERCENTS}
+ #{ALPHA_CHANNEL}
+ \)
+ )
+ /xi
+
+ FORMATS = [HEX_FORMAT, RGB_FORMAT, HSL_FORMAT].freeze
+
+ class << self
+ # Public: Analyzes whether the String is a color code.
+ #
+ # text - The String to be parsed.
+ #
+ # Returns the recognized color String or nil if none was found.
+ def parse(text)
+ text if color_format =~ text
+ end
+
+ private
+
+ def color_format
+ @color_format ||= /\A(#{Regexp.union(FORMATS)})\z/ix
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/color_filter.rb b/lib/banzai/filter/color_filter.rb
new file mode 100644
index 00000000000..6ab29ac281f
--- /dev/null
+++ b/lib/banzai/filter/color_filter.rb
@@ -0,0 +1,31 @@
+module Banzai
+ module Filter
+ # HTML filter that renders `color` followed by a color "chip".
+ #
+ class ColorFilter < HTML::Pipeline::Filter
+ COLOR_CHIP_CLASS = 'gfm-color_chip'.freeze
+
+ def call
+ doc.css('code').each do |node|
+ color = ColorParser.parse(node.content)
+ node << color_chip(color) if color
+ end
+
+ doc
+ end
+
+ private
+
+ def color_chip(color)
+ checkerboard = doc.document.create_element('span', class: COLOR_CHIP_CLASS)
+ chip = doc.document.create_element('span', style: inline_styles(color: color))
+
+ checkerboard << chip
+ end
+
+ def inline_styles(color:)
+ "background-color: #{color};"
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/broadcast_message_pipeline.rb b/lib/banzai/pipeline/broadcast_message_pipeline.rb
index adc09c8afbd..5dd572de3a1 100644
--- a/lib/banzai/pipeline/broadcast_message_pipeline.rb
+++ b/lib/banzai/pipeline/broadcast_message_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
Filter::SanitizationFilter,
Filter::EmojiFilter,
+ Filter::ColorFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter
]
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index c746f6f64e9..4001b8a85e3 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -14,6 +14,7 @@ module Banzai
Filter::SyntaxHighlightFilter,
Filter::MathFilter,
+ Filter::ColorFilter,
Filter::MermaidFilter,
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index a2b78a5e021..f13d78d24e3 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -259,6 +259,10 @@ describe 'GitLab Markdown' do
it 'includes VideoLinkFilter' do
expect(doc).to parse_video_links
end
+
+ it 'includes ColorFilter' do
+ expect(doc).to parse_colors
+ end
end
context 'wiki pipeline' do
@@ -320,6 +324,10 @@ describe 'GitLab Markdown' do
it 'includes VideoLinkFilter' do
expect(doc).to parse_video_links
end
+
+ it 'includes ColorFilter' do
+ expect(doc).to parse_colors
+ end
end
# Fake a `current_user` helper
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 71abb6da607..da32a46675f 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -280,6 +280,18 @@ However the wrapping tags cannot be mixed as such:
![My Video](/assets/videos/gitlab-demo.mp4)
+### Colors
+
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.7)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.7)`
+
### Mermaid
> If this is not rendered correctly, see
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 6f357306ec7..182880d42e9 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -131,6 +131,7 @@ describe('GfmAutoComplete', function () {
describe('should not match special sequences', () => {
const ShouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
+ const ShouldNotBePrependedBy = ['`'];
flagsUseDefaultMatcher.forEach((atSign) => {
ShouldNotBeFollowedBy.forEach((followedSymbol) => {
@@ -140,6 +141,14 @@ describe('GfmAutoComplete', function () {
expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
});
});
+
+ ShouldNotBePrependedBy.forEach((prependedSymbol) => {
+ const seq = prependedSymbol + atSign;
+
+ it(`should not match "${seq}"`, () => {
+ expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
+ });
+ });
});
});
});
diff --git a/spec/lib/banzai/color_parser_spec.rb b/spec/lib/banzai/color_parser_spec.rb
new file mode 100644
index 00000000000..a1cb0c07b06
--- /dev/null
+++ b/spec/lib/banzai/color_parser_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+
+describe Banzai::ColorParser do
+ describe '.parse' do
+ context 'HEX format' do
+ [
+ '#abc', '#ABC',
+ '#d2d2d2', '#D2D2D2',
+ '#123a', '#123A',
+ '#123456aa', '#123456AA'
+ ].each do |color|
+ it "parses the valid hex color #{color}" do
+ expect(subject.parse(color)).to eq(color)
+ end
+ end
+
+ [
+ '#', '#1', '#12', '#12g', '#12G',
+ '#12345', '#r2r2r2', '#R2R2R2', '#1234567',
+ '# 123', '# 1234', '# 123456', '# 12345678',
+ '#1 2 3', '#123 4', '#12 34 56', '#123456 78'
+ ].each do |color|
+ it "does not parse the invalid hex color #{color}" do
+ expect(subject.parse(color)).to be_nil
+ end
+ end
+ end
+
+ context 'RGB format' do
+ [
+ 'rgb(0,0,0)', 'rgb(255,255,255)',
+ 'rgb(0, 0, 0)', 'RGB(0,0,0)',
+ 'rgb(0,0,0,0)', 'rgb(0,0,0,0.0)', 'rgb(0,0,0,.0)',
+ 'rgb(0,0,0, 0)', 'rgb(0,0,0, 0.0)', 'rgb(0,0,0, .0)',
+ 'rgb(0,0,0,1)', 'rgb(0,0,0,1.0)',
+ 'rgba(0,0,0)', 'rgba(0,0,0,0)', 'RGBA(0,0,0)',
+ 'rgb(0%,0%,0%)', 'rgba(0%,0%,0%,0%)'
+ ].each do |color|
+ it "parses the valid rgb color #{color}" do
+ expect(subject.parse(color)).to eq(color)
+ end
+ end
+
+ [
+ 'FOOrgb(0,0,0)', 'rgb(0,0,0)BAR',
+ 'rgb(0,0,-1)', 'rgb(0,0,-0)', 'rgb(0,0,256)',
+ 'rgb(0,0,0,-0.1)', 'rgb(0,0,0,-0.0)', 'rgb(0,0,0,-.1)',
+ 'rgb(0,0,0,1.1)', 'rgb(0,0,0,2)',
+ 'rgba(0,0,0,)', 'rgba(0,0,0,0.)', 'rgba(0,0,0,1.)',
+ 'rgb(0,0,0%)', 'rgb(101%,0%,0%)'
+ ].each do |color|
+ it "does not parse the invalid rgb color #{color}" do
+ expect(subject.parse(color)).to be_nil
+ end
+ end
+ end
+
+ context 'HSL format' do
+ [
+ 'hsl(0,0%,0%)', 'hsl(0,100%,100%)',
+ 'hsl(540,0%,0%)', 'hsl(-720,0%,0%)',
+ 'hsl(0deg,0%,0%)', 'hsl(0DEG,0%,0%)',
+ 'hsl(0, 0%, 0%)', 'HSL(0,0%,0%)',
+ 'hsl(0,0%,0%,0)', 'hsl(0,0%,0%,0.0)', 'hsl(0,0%,0%,.0)',
+ 'hsl(0,0%,0%, 0)', 'hsl(0,0%,0%, 0.0)', 'hsl(0,0%,0%, .0)',
+ 'hsl(0,0%,0%,1)', 'hsl(0,0%,0%,1.0)',
+ 'hsla(0,0%,0%)', 'hsla(0,0%,0%,0)', 'HSLA(0,0%,0%)',
+ 'hsl(1rad,0%,0%)', 'hsl(1.1rad,0%,0%)', 'hsl(.1rad,0%,0%)',
+ 'hsl(-1rad,0%,0%)', 'hsl(1RAD,0%,0%)'
+ ].each do |color|
+ it "parses the valid hsl color #{color}" do
+ expect(subject.parse(color)).to eq(color)
+ end
+ end
+
+ [
+ 'hsl(+0,0%,0%)', 'hsl(0,0,0%)', 'hsl(0,0%,0)', 'hsl(0 deg,0%,0%)',
+ 'hsl(0,-0%,0%)', 'hsl(0,101%,0%)', 'hsl(0,-1%,0%)',
+ 'hsl(0,0%,0%,-0.1)', 'hsl(0,0%,0%,-.1)',
+ 'hsl(0,0%,0%,1.1)', 'hsl(0,0%,0%,2)',
+ 'hsl(0,0%,0%,)', 'hsl(0,0%,0%,0.)', 'hsl(0,0%,0%,1.)',
+ 'hsl(deg,0%,0%)', 'hsl(rad,0%,0%)'
+ ].each do |color|
+ it "does not parse the invalid hsl color #{color}" do
+ expect(subject.parse(color)).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/color_filter_spec.rb b/spec/lib/banzai/filter/color_filter_spec.rb
new file mode 100644
index 00000000000..a098b037510
--- /dev/null
+++ b/spec/lib/banzai/filter/color_filter_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ColorFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:color) { '#F00' }
+ let(:color_chip_selector) { 'code > span.gfm-color_chip > span' }
+
+ ['#123', '#1234', '#123456', '#12345678',
+ 'rgb(0,0,0)', 'RGB(0, 0, 0)', 'rgba(0,0,0,1)', 'RGBA(0,0,0,0.7)',
+ 'hsl(270,30%,50%)', 'HSLA(270, 30%, 50%, .7)'].each do |color|
+ it "inserts color chip for supported color format #{color}" do
+ content = code_tag(color)
+ doc = filter(content)
+ color_chip = doc.at_css(color_chip_selector)
+
+ expect(color_chip.content).to be_empty
+ expect(color_chip.parent[:class]).to eq 'gfm-color_chip'
+ expect(color_chip[:style]).to eq "background-color: #{color};"
+ end
+ end
+
+ it 'ignores valid color code without backticks(code tags)' do
+ doc = filter(color)
+
+ expect(doc.css('span.gfm-color_chip').size).to be_zero
+ end
+
+ it 'ignores valid color code with prepended space' do
+ content = code_tag(' ' + color)
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ it 'ignores valid color code with appended space' do
+ content = code_tag(color + ' ')
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ it 'ignores valid color code surrounded by spaces' do
+ content = code_tag(' ' + color + ' ')
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ it 'ignores invalid color code' do
+ invalid_color = '#BAR'
+ content = code_tag(invalid_color)
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ def code_tag(string)
+ "#{string}
"
+ end
+end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index d12b2757427..ec4ec6f4038 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -190,6 +190,27 @@ module MarkdownMatchers
expect(video['src']).to end_with('/assets/videos/gitlab-demo.mp4')
end
end
+
+ # ColorFilter
+ matcher :parse_colors do
+ set_default_markdown_messages
+
+ match do |actual|
+ color_chips = actual.css('code > span.gfm-color_chip > span')
+
+ expect(color_chips.count).to eq(9)
+
+ [
+ '#F00', '#F00A', '#FF0000', '#FF0000AA', 'RGB(0,255,0)',
+ 'RGB(0%,100%,0%)', 'RGBA(0,255,0,0.7)', 'HSL(540,70%,50%)',
+ 'HSLA(540,70%,50%,0.7)'
+ ].each_with_index do |color, i|
+ parsed_color = Banzai::ColorParser.parse(color)
+ expect(color_chips[i]['style']).to match("background-color: #{parsed_color};")
+ expect(color_chips[i].parent.parent.content).to match(color)
+ end
+ end
+ end
end
# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
--
cgit v1.2.1
From 9c10a71e13bedb78be8da0cfdf4f0c6250b8d190 Mon Sep 17 00:00:00 2001
From: Mark Fletcher
Date: Mon, 29 Jan 2018 15:22:53 +0000
Subject: Document format parameter for Repository Archive endpoint
---
doc/api/repositories.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 5fb25e40ed7..96609cd530f 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -114,6 +114,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified
+- `format` (optional) - The archive format. Default is `tar.gz`. Options are `tar.gz`, `tar.bz2`, `tbz`, `tbz2`, `tb2`, `bz2`, `tar`, `zip`
## Compare branches, tags or commits
--
cgit v1.2.1
From e407c2d18f6558041a5bd54a568cfe52c1f3e3a4 Mon Sep 17 00:00:00 2001
From: "Jacob Vosmaer (GitLab)"
Date: Mon, 29 Jan 2018 15:27:46 +0000
Subject: Don't allow Repository#log with limit zero
---
lib/gitlab/git/repository.rb | 6 +++++-
spec/features/commits_spec.rb | 2 +-
spec/lib/gitlab/git/repository_spec.rb | 10 ++++++++++
spec/models/repository_spec.rb | 30 +++++++++++++++---------------
spec/requests/api/commits_spec.rb | 12 ++++++------
spec/requests/api/v3/commits_spec.rb | 6 +++---
6 files changed, 40 insertions(+), 26 deletions(-)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 64b491517cb..3a7930154e5 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -468,9 +468,13 @@ module Gitlab
}
options = default_options.merge(options)
- options[:limit] ||= 0
options[:offset] ||= 0
+ limit = options[:limit]
+ if limit == 0 || !limit.is_a?(Integer)
+ raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
+ end
+
gitaly_migrate(:find_commits) do |is_enabled|
if is_enabled
gitaly_commit_client.find_commits(options)
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index a28b8905b65..62a2ec55b00 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -194,7 +194,7 @@ describe 'Commits' do
end
it 'includes the committed_date for each commit' do
- commits = project.repository.commits(branch_name)
+ commits = project.repository.commits(branch_name, limit: 40)
commits.each do |commit|
expect(page).to have_content("authored #{commit.authored_date.strftime("%b %d, %Y")}")
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 3db04d99855..935d1df6dad 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -981,6 +981,16 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
end
+
+ context 'limit validation' do
+ where(:limit) do
+ [0, nil, '', 'foo']
+ end
+
+ with_them do
+ it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
+ end
+ end
end
describe "#rugged_commits_between" do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 7c61c6b7299..d4070b320ed 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -222,20 +222,20 @@ describe Repository do
it 'sets follow when path is a single path' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice
- repository.commits('master', path: 'README.md')
- repository.commits('master', path: ['README.md'])
+ repository.commits('master', limit: 1, path: 'README.md')
+ repository.commits('master', limit: 1, path: ['README.md'])
end
it 'does not set follow when path is multiple paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
- repository.commits('master', path: ['README.md', 'CHANGELOG'])
+ repository.commits('master', limit: 1, path: ['README.md', 'CHANGELOG'])
end
it 'does not set follow when there are no paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
- repository.commits('master')
+ repository.commits('master', limit: 1)
end
end
@@ -455,7 +455,7 @@ describe Repository do
expect do
repository.create_dir(user, 'newdir',
message: 'Create newdir', branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
newdir = repository.tree('master', 'newdir')
expect(newdir.path).to eq('newdir')
@@ -469,7 +469,7 @@ describe Repository do
repository.create_dir(user, 'newdir',
message: 'Create newdir', branch_name: 'patch',
start_branch_name: 'master', start_project: forked_project)
- end.to change { repository.commits('master').count }.by(0)
+ end.to change { repository.count_commits(ref: 'master') }.by(0)
expect(repository.branch_exists?('patch')).to be_truthy
expect(forked_project.repository.branch_exists?('patch')).to be_falsy
@@ -486,7 +486,7 @@ describe Repository do
message: 'Add newdir',
branch_name: 'master',
author_email: author_email, author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
@@ -502,7 +502,7 @@ describe Repository do
repository.create_file(user, 'NEWCHANGELOG', 'Changelog!',
message: 'Create changelog',
branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
blob = repository.blob_at('master', 'NEWCHANGELOG')
@@ -514,7 +514,7 @@ describe Repository do
repository.create_file(user, 'new_dir/new_file.txt', 'File!',
message: 'Create new_file with new_dir',
branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
expect(repository.tree('master', 'new_dir').path).to eq('new_dir')
expect(repository.blob_at('master', 'new_dir/new_file.txt').data).to eq('File!')
@@ -538,7 +538,7 @@ describe Repository do
branch_name: 'master',
author_email: author_email,
author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
@@ -554,7 +554,7 @@ describe Repository do
repository.update_file(user, 'CHANGELOG', 'Changelog!',
message: 'Update changelog',
branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
blob = repository.blob_at('master', 'CHANGELOG')
@@ -567,7 +567,7 @@ describe Repository do
branch_name: 'master',
previous_path: 'LICENSE',
message: 'Changes filename')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
files = repository.ls_files('master')
@@ -584,7 +584,7 @@ describe Repository do
message: 'Update README',
author_email: author_email,
author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
@@ -599,7 +599,7 @@ describe Repository do
expect do
repository.delete_file(user, 'README',
message: 'Remove README', branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
expect(repository.blob_at('master', 'README')).to be_nil
end
@@ -610,7 +610,7 @@ describe Repository do
repository.delete_file(user, 'README',
message: 'Remove README', branch_name: 'master',
author_email: author_email, author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 34db50dc082..ff5f207487b 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -62,7 +62,7 @@ describe API::Commits do
context "since optional parameter" do
it "returns project commits since provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
@@ -73,7 +73,7 @@ describe API::Commits do
end
it 'include correct pagination headers' do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
@@ -87,12 +87,12 @@ describe API::Commits do
context "until optional parameter" do
it "returns project commits until provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 20)
before = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
- if commits.size >= 20
+ if commits.size == 20
expect(json_response.size).to eq(20)
else
expect(json_response.size).to eq(commits.size - 1)
@@ -103,7 +103,7 @@ describe API::Commits do
end
it 'include correct pagination headers' do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
before = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
@@ -181,7 +181,7 @@ describe API::Commits do
let(:page) { 3 }
it 'returns the third 5 commits' do
- commit = project.repository.commits('HEAD', offset: (page - 1) * per_page).first
+ commit = project.repository.commits('HEAD', limit: per_page, offset: (page - 1) * per_page).first
expect(json_response.size).to eq(per_page)
expect(json_response.first['id']).to eq(commit.id)
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 34c543bffe8..9ef3b859001 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -36,7 +36,7 @@ describe API::V3::Commits do
context "since optional parameter" do
it "returns project commits since provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
since = commits.second.created_at
get v3_api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
@@ -49,12 +49,12 @@ describe API::V3::Commits do
context "until optional parameter" do
it "returns project commits until provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 20)
before = commits.second.created_at
get v3_api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
- if commits.size >= 20
+ if commits.size == 20
expect(json_response.size).to eq(20)
else
expect(json_response.size).to eq(commits.size - 1)
--
cgit v1.2.1
From bf01452cc361842dab7442a9ad868213d53bd212 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin
Date: Mon, 29 Jan 2018 23:46:45 +0800
Subject: Define selectors and remove EE only code
---
qa/qa/page/admin/settings.rb | 13 ++++++-----
qa/qa/page/group/show.rb | 52 ++++++++++++++++++++++++++++----------------
qa/qa/page/menu/admin.rb | 11 ++--------
3 files changed, 42 insertions(+), 34 deletions(-)
diff --git a/qa/qa/page/admin/settings.rb b/qa/qa/page/admin/settings.rb
index 1904732aee6..1f646103e7f 100644
--- a/qa/qa/page/admin/settings.rb
+++ b/qa/qa/page/admin/settings.rb
@@ -2,12 +2,13 @@ module QA
module Page
module Admin
class Settings < Page::Base
- ##
- # TODO, define all selectors required by this page object
- #
- # See gitlab-org/gitlab-qa#154
- #
- view 'app/views/admin/application_settings/show.html.haml'
+ view 'app/views/admin/application_settings/_form.html.haml' do
+ element :form_actions, '.form-actions'
+ element :submit, "submit 'Save'"
+ element :repository_storage, '%legend Repository Storage'
+ element :hashed_storage,
+ 'Create new projects using hashed storage paths'
+ end
def enable_hashed_storage
scroll_to 'legend', text: 'Repository Storage'
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index f23294145dd..ac1fd9b5f90 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -2,12 +2,25 @@ 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 :dropdown_toggle, '.dropdown-toggle'
+ element :new_project_subgroup, '.new-project-subgroup'
+
+ element :new_project_toggle,
+ /%li.+ data: { value: "new\-project"/
+ element :new_project_button,
+ /%input.+ data: { action: "new\-project"/
+
+ element :new_subgroup_toggle,
+ /%li.+ data: { value: "new\-subgroup"/
+ # TODO: input[data-action='new-subgroup'] seems to be handled by JS?
+ # See app/assets/javascripts/groups/new_group_child.js
+ end
+
+ view 'app/views/shared/groups/_search_form.html.haml' do
+ element :filter_by_name,
+ "placeholder: s_('GroupsTree|Filter by name...')"
+ end
def go_to_subgroup(name)
click_link name
@@ -24,31 +37,32 @@ module QA
end
def go_to_new_subgroup
- within '.new-project-subgroup' do
- # May need to click again because it is possible to click the button quicker than the JS is bound
- wait(reload: false) do
- find('.dropdown-toggle').click
-
- page.has_css?("li[data-value='new-subgroup']")
- end
- find("li[data-value='new-subgroup']").click
- end
+ click_new('subgroup')
find("input[data-action='new-subgroup']").click
end
def go_to_new_project
+ click_new('project')
+
+ find("input[data-action='new-project']").click
+ end
+
+ private
+
+ def click_new(kind)
within '.new-project-subgroup' do
+ css = "li[data-value='new-#{kind}']"
+
# May need to click again because it is possible to click the button quicker than the JS is bound
wait(reload: false) do
find('.dropdown-toggle').click
- page.has_css?("li[data-value='new-project']")
+ page.has_css?(css)
end
- find("li[data-value='new-project']").click
- end
- find("input[data-action='new-project']").click
+ find(css).click
+ end
end
end
end
diff --git a/qa/qa/page/menu/admin.rb b/qa/qa/page/menu/admin.rb
index 40da4a53e8a..573b98f7386 100644
--- a/qa/qa/page/menu/admin.rb
+++ b/qa/qa/page/menu/admin.rb
@@ -2,15 +2,8 @@ module QA
module Page
module Menu
class Admin < Page::Base
- ##
- # TODO, define all selectors required by this page object
- #
- # See gitlab-org/gitlab-qa#154
- #
- view 'app/views/admin/dashboard/index.html.haml'
-
- def go_to_license
- click_link 'License'
+ view 'app/views/layouts/nav/sidebar/_admin.html.haml' do
+ element :settings, "_('Settings')"
end
def go_to_settings
--
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 ba55dfd6bfb42d8eca74f9805b10e3ac2d84cfe8 Mon Sep 17 00:00:00 2001
From: Clement Ho
Date: Mon, 29 Jan 2018 10:38:14 -0600
Subject: Contribution calendar label cutoff fix
---
app/assets/javascripts/users/activity_calendar.js | 2 +-
changelogs/unreleased/contribution_calendar_label_cut_off.yml | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/contribution_calendar_label_cut_off.yml
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js
index 0581239d5a5..0ca54faa71c 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/users/activity_calendar.js
@@ -98,7 +98,7 @@ export default class ActivityCalendar {
const secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
if (lastColMonth !== secondLastColMonth) {
- extraWidthPadding = 3;
+ extraWidthPadding = 6;
}
return extraWidthPadding;
diff --git a/changelogs/unreleased/contribution_calendar_label_cut_off.yml b/changelogs/unreleased/contribution_calendar_label_cut_off.yml
new file mode 100644
index 00000000000..0b4a746bab8
--- /dev/null
+++ b/changelogs/unreleased/contribution_calendar_label_cut_off.yml
@@ -0,0 +1,5 @@
+---
+title: Contribution calendar label was cut off
+merge_request:
+author: Branka Martinovic
+type: fixed
--
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 dbfedb5adb3651d3b700186b24a5b1b69d10a0dd Mon Sep 17 00:00:00 2001
From: Oswaldo Ferreira
Date: Mon, 29 Jan 2018 23:53:47 -0200
Subject: Check MR state before submitting queries for discussion state
---
app/models/merge_request.rb | 4 ++--
app/serializers/merge_request_widget_entity.rb | 13 ++++++++++++-
.../osw-short-circuit-mergeable-disccusions-state.yml | 5 +++++
spec/models/merge_request_spec.rb | 4 ++++
4 files changed, 23 insertions(+), 3 deletions(-)
create mode 100644 changelogs/unreleased/osw-short-circuit-mergeable-disccusions-state.yml
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f6d4843abc3..d025062f562 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -618,12 +618,12 @@ class MergeRequest < ActiveRecord::Base
can_be_merged? && !should_be_rebased?
end
- def mergeable_state?(skip_ci_check: false)
+ def mergeable_state?(skip_ci_check: false, skip_discussions_check: false)
return false unless open?
return false if work_in_progress?
return false if broken?
return false unless skip_ci_check || mergeable_ci_state?
- return false unless mergeable_discussions_state?
+ return false unless skip_discussions_check || mergeable_discussions_state?
true
end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 48cd2317f46..fbfe480503b 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -48,7 +48,18 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_ongoing?, as: :merge_ongoing
expose :work_in_progress?, as: :work_in_progress
expose :source_branch_exists?, as: :source_branch_exists
- expose :mergeable_discussions_state?, as: :mergeable_discussions_state
+
+ expose :mergeable_discussions_state?, as: :mergeable_discussions_state do |merge_request|
+ # This avoids calling MergeRequest#mergeable_discussions_state without
+ # considering the state of the MR first. If a MR isn't mergeable, we can
+ # safely short-circuit it.
+ if merge_request.mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
+ merge_request.mergeable_discussions_state?
+ else
+ false
+ end
+ end
+
expose :branch_missing?, as: :branch_missing
expose :commits_count
expose :cannot_be_merged?, as: :has_conflicts
diff --git a/changelogs/unreleased/osw-short-circuit-mergeable-disccusions-state.yml b/changelogs/unreleased/osw-short-circuit-mergeable-disccusions-state.yml
new file mode 100644
index 00000000000..62931218861
--- /dev/null
+++ b/changelogs/unreleased/osw-short-circuit-mergeable-disccusions-state.yml
@@ -0,0 +1,5 @@
+---
+title: Stop checking if discussions are in a mergeable state if the MR isn't
+merge_request:
+author:
+type: performance
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index eb9690df313..243eeddc7a8 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1339,6 +1339,10 @@ describe MergeRequest do
it 'returns false' do
expect(subject.mergeable_state?).to be_falsey
end
+
+ it 'returns true when skipping discussions check' do
+ expect(subject.mergeable_state?(skip_discussions_check: true)).to be(true)
+ end
end
end
end
--
cgit v1.2.1
From 9af1773f8d6b6633938c6732bf28de83a2b88cc3 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 25 Jan 2018 16:40:31 +0000
Subject: Converted API.js to axios
---
app/assets/javascripts/api.js | 115 +++++---------
app/assets/javascripts/ide/stores/actions.js | 6 +-
.../javascripts/ide/stores/actions/branch.js | 2 +-
app/assets/javascripts/lib/utils/users_cache.js | 8 +-
spec/javascripts/api_spec.js | 171 +++++++--------------
5 files changed, 102 insertions(+), 200 deletions(-)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 7cb81bf4d5b..bd2842c90f5 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,4 +1,3 @@
-import $ from 'jquery';
import axios from './lib/utils/axios_utils';
const Api = {
@@ -23,38 +22,32 @@ const Api = {
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId);
- return $.ajax({
- url,
- dataType: 'json',
- })
- .done(group => callback(group));
+ return axios.get(url)
+ .then(({ data }) => callback(data));
},
// Return groups list. Filtered by query
groups(query, options, callback) {
const url = Api.buildUrl(Api.groupsPath);
- return $.ajax({
- url,
- data: Object.assign({
+ return axios.get(url, {
+ params: Object.assign({
search: query,
per_page: 20,
}, options),
- dataType: 'json',
})
- .done(groups => callback(groups));
+ .then(({ data }) => callback(data));
},
// Return namespaces list. Filtered by query
namespaces(query, callback) {
const url = Api.buildUrl(Api.namespacesPath);
- return $.ajax({
- url,
- data: {
+ return axios.get(url, {
+ params: {
search: query,
per_page: 20,
},
- dataType: 'json',
- }).done(namespaces => callback(namespaces));
+ })
+ .then(({ data }) => callback(data));
},
// Return projects list. Filtered by query
@@ -70,12 +63,10 @@ const Api = {
defaults.membership = true;
}
- return $.ajax({
- url,
- data: Object.assign(defaults, options),
- dataType: 'json',
+ return axios.get(url, {
+ params: Object.assign(defaults, options),
})
- .done(projects => callback(projects));
+ .then(({ data }) => callback(data));
},
// Return single project
@@ -97,41 +88,34 @@ const Api = {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
}
- return $.ajax({
- url,
- type: 'POST',
- data: { label: data },
- dataType: 'json',
+ return axios.post(url, {
+ label: data,
})
- .done(label => callback(label))
- .fail(message => callback(message.responseJSON));
+ .then(res => callback(res.data))
+ .catch(e => callback(e.response.data));
},
// Return group projects list. Filtered by query
groupProjects(groupId, query, callback) {
const url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', groupId);
- return $.ajax({
- url,
- data: {
+ return axios.get(url, {
+ params: {
search: query,
per_page: 20,
},
- dataType: 'json',
})
- .done(projects => callback(projects));
+ .then(({ data }) => callback(data));
},
commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath)
.replace(':id', encodeURIComponent(id));
- return this.wrapAjaxCall({
- url,
- type: 'POST',
- contentType: 'application/json; charset=utf-8',
- data: JSON.stringify(data),
- dataType: 'json',
+ return axios.post(url, JSON.stringify(data), {
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
});
},
@@ -140,40 +124,37 @@ const Api = {
.replace(':id', encodeURIComponent(id))
.replace(':branch', branch);
- return this.wrapAjaxCall({
- url,
- type: 'GET',
- contentType: 'application/json; charset=utf-8',
- dataType: 'json',
- });
+ return axios.get(url);
},
// Return text for a specific license
licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath)
.replace(':key', key);
- return $.ajax({
- url,
- data,
+ return axios.get(url, {
+ params: data,
})
- .done(license => callback(license));
+ .then(res => callback(res.data));
},
gitignoreText(key, callback) {
const url = Api.buildUrl(Api.gitignorePath)
.replace(':key', key);
- return $.get(url, gitignore => callback(gitignore));
+ return axios.get(url)
+ .then(({ data }) => callback(data));
},
gitlabCiYml(key, callback) {
const url = Api.buildUrl(Api.gitlabCiYmlPath)
.replace(':key', key);
- return $.get(url, file => callback(file));
+ return axios.get(url)
+ .then(({ data }) => callback(data));
},
dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
- $.get(url, callback);
+ return axios.get(url)
+ .then(({ data }) => callback(data));
},
issueTemplate(namespacePath, projectPath, key, type, callback) {
@@ -182,23 +163,18 @@ const Api = {
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
- $.ajax({
- url,
- dataType: 'json',
- })
- .done(file => callback(null, file))
- .fail(callback);
+ return axios.get(url)
+ .then(({ data }) => callback(null, data))
+ .catch(callback);
},
users(query, options) {
const url = Api.buildUrl(this.usersPath);
- return Api.wrapAjaxCall({
- url,
- data: Object.assign({
+ return axios.get(url, {
+ params: Object.assign({
search: query,
per_page: 20,
}, options),
- dataType: 'json',
});
},
@@ -209,21 +185,6 @@ const Api = {
}
return urlRoot + url.replace(':version', gon.api_version);
},
-
- wrapAjaxCall(options) {
- return new Promise((resolve, reject) => {
- // jQuery 2 is not Promises/A+ compatible (missing catch)
- $.ajax(options) // eslint-disable-line promise/catch-or-return
- .then(data => resolve(data),
- (jqXHR, textStatus, errorThrown) => {
- const error = new Error(`${options.url}: ${errorThrown}`);
- error.textStatus = textStatus;
- if (jqXHR && jqXHR.responseJSON) error.responseJSON = jqXHR.responseJSON;
- reject(error);
- },
- );
- });
- },
};
export default Api;
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 96a87744df5..69bd18e46b8 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -90,7 +90,7 @@ export const commitChanges = (
) =>
service
.commit(state.currentProjectId, payload)
- .then((data) => {
+ .then(({ data }) => {
const { branch } = payload;
if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true);
@@ -147,8 +147,8 @@ export const commitChanges = (
})
.catch((err) => {
let errMsg = 'Error committing changes. Please try again.';
- if (err.responseJSON && err.responseJSON.message) {
- errMsg += ` (${stripHtml(err.responseJSON.message)})`;
+ if (err.response.data && err.response.data.message) {
+ errMsg += ` (${stripHtml(err.response.data.message)})`;
}
flash(errMsg, 'alert', document, null, false, true);
window.dispatchEvent(new Event('resize'));
diff --git a/app/assets/javascripts/ide/stores/actions/branch.js b/app/assets/javascripts/ide/stores/actions/branch.js
index 589ec28c6a4..bc6fd2d4163 100644
--- a/app/assets/javascripts/ide/stores/actions/branch.js
+++ b/app/assets/javascripts/ide/stores/actions/branch.js
@@ -10,7 +10,7 @@ export const getBranchData = (
!state.projects[`${projectId}`].branches[branchId])
|| force) {
service.getBranchData(`${projectId}`, branchId)
- .then((data) => {
+ .then(({ data }) => {
const { id } = data.commit;
commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index 88f8a622c00..b01ec6b81a3 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -8,16 +8,16 @@ class UsersCache extends Cache {
}
return Api.users('', { username })
- .then((users) => {
- if (!users.length) {
+ .then(({ data }) => {
+ if (!data.length) {
throw new Error(`User "${username}" could not be found!`);
}
- if (users.length > 1) {
+ if (data.length > 1) {
throw new Error(`Expected username "${username}" to be unique!`);
}
- const user = users[0];
+ const user = data[0];
this.internalStorage[username] = user;
return user;
});
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index cc5fa42aafe..2e45c5e2712 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -1,3 +1,5 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
describe('Api', () => {
@@ -7,20 +9,17 @@ describe('Api', () => {
api_version: dummyApiVersion,
relative_url_root: dummyUrlRoot,
};
- const dummyResponse = 'hello from outer space!';
- const sendDummyResponse = () => {
- const deferred = $.Deferred();
- deferred.resolve(dummyResponse);
- return deferred.promise();
- };
let originalGon;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
originalGon = window.gon;
window.gon = Object.assign({}, dummyGon);
});
afterEach(() => {
+ mock.restore();
window.gon = originalGon;
});
@@ -39,14 +38,12 @@ describe('Api', () => {
it('fetches a group', (done) => {
const groupId = '123456';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}.json`;
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- return sendDummyResponse();
+ mock.onGet(expectedUrl).reply(200, {
+ name: 'test',
});
Api.group(groupId, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.name).toBe('test');
done();
});
});
@@ -57,19 +54,13 @@ describe('Api', () => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.groups(query, options, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -79,19 +70,13 @@ describe('Api', () => {
it('fetches namespaces', (done) => {
const query = 'dummy query';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
- const expectedData = {
- search: query,
- per_page: 20,
- };
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.namespaces(query, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -103,21 +88,13 @@ describe('Api', () => {
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
window.gon.current_user_id = 1;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- membership: true,
- simple: true,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.projects(query, options, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -126,20 +103,13 @@ describe('Api', () => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- simple: true,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.projects(query, options, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -154,16 +124,16 @@ describe('Api', () => {
const expectedData = {
label: labelData,
};
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.type).toEqual('POST');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
+ mock.onPost(expectedUrl).reply((config) => {
+ expect(config.data).toBe(JSON.stringify(expectedData));
+
+ return [200, {
+ name: 'test',
+ }];
});
Api.newLabel(namespace, project, labelData, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.name).toBe('test');
done();
});
});
@@ -174,19 +144,13 @@ describe('Api', () => {
const groupId = '123456';
const query = 'dummy query';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
- const expectedData = {
- search: query,
- per_page: 20,
- };
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.groupProjects(groupId, query, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -197,14 +161,10 @@ describe('Api', () => {
const licenseKey = "driver's license";
const data = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/licenses/${licenseKey}`;
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.data).toEqual(data);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.licenseText(licenseKey, data, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -214,13 +174,10 @@ describe('Api', () => {
it('fetches a gitignore text', (done) => {
const gitignoreKey = 'ignore git';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitignores/${gitignoreKey}`;
- spyOn(jQuery, 'get').and.callFake((url, callback) => {
- expect(url).toEqual(expectedUrl);
- callback(dummyResponse);
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.gitignoreText(gitignoreKey, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -230,13 +187,10 @@ describe('Api', () => {
it('fetches a .gitlab-ci.yml', (done) => {
const gitlabCiYmlKey = 'Y CI ML';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitlab_ci_ymls/${gitlabCiYmlKey}`;
- spyOn(jQuery, 'get').and.callFake((url, callback) => {
- expect(url).toEqual(expectedUrl);
- callback(dummyResponse);
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.gitlabCiYml(gitlabCiYmlKey, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -246,13 +200,10 @@ describe('Api', () => {
it('fetches a Dockerfile', (done) => {
const dockerfileYmlKey = 'a giant whale';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/dockerfiles/${dockerfileYmlKey}`;
- spyOn(jQuery, 'get').and.callFake((url, callback) => {
- expect(url).toEqual(expectedUrl);
- callback(dummyResponse);
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.dockerfileYml(dockerfileYmlKey, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -265,14 +216,10 @@ describe('Api', () => {
const templateKey = ' template #%?.key ';
const templateType = 'template type';
const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(templateKey)}`;
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
- expect(error).toBe(null);
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -283,20 +230,14 @@ describe('Api', () => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.users(query, options)
- .then((response) => {
- expect(response).toBe(dummyResponse);
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].name).toBe('test');
})
.then(done)
.catch(done.fail);
--
cgit v1.2.1
From e0b4d919cd8c97e9c6e96304e0770737c6bb3e52 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 08:49:12 +0000
Subject: spec fixes after API axios change
---
spec/javascripts/lib/utils/users_cache_spec.js | 4 +++-
.../repo/components/repo_commit_section_spec.js | 6 ++++--
spec/javascripts/repo/stores/actions_spec.js | 20 ++++++++++++--------
3 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/spec/javascripts/lib/utils/users_cache_spec.js b/spec/javascripts/lib/utils/users_cache_spec.js
index ec6ea35952b..50371c8c5f6 100644
--- a/spec/javascripts/lib/utils/users_cache_spec.js
+++ b/spec/javascripts/lib/utils/users_cache_spec.js
@@ -92,7 +92,9 @@ describe('UsersCache', () => {
apiSpy = (query, options) => {
expect(query).toBe('');
expect(options).toEqual({ username: dummyUsername });
- return Promise.resolve([dummyUser]);
+ return Promise.resolve({
+ data: [dummyUser],
+ });
};
UsersCache.retrieve(dummyUsername)
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index 676ac09f2c9..93e94b4f24c 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -87,8 +87,10 @@ describe('RepoCommitSection', () => {
changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles));
spyOn(service, 'commit').and.returnValue(Promise.resolve({
- short_id: '1',
- stats: {},
+ data: {
+ short_id: '1',
+ stats: {},
+ },
}));
});
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
index 8d830c67290..73a8dcd7a89 100644
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ b/spec/javascripts/repo/stores/actions_spec.js
@@ -250,13 +250,15 @@ describe('Multi-file store actions', () => {
describe('success', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
- id: '123456',
- short_id: '123',
- message: 'test message',
- committed_date: 'date',
- stats: {
- additions: '1',
- deletions: '2',
+ data: {
+ id: '123456',
+ short_id: '123',
+ message: 'test message',
+ committed_date: 'date',
+ stats: {
+ additions: '1',
+ deletions: '2',
+ },
},
}));
});
@@ -324,7 +326,9 @@ describe('Multi-file store actions', () => {
describe('failed', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
- message: 'failed message',
+ data: {
+ message: 'failed message',
+ },
}));
});
--
cgit v1.2.1
From a16cdd0b3eeae3e9af722c417a6c5fab36f228d1 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 09:19:03 +0000
Subject: converted blob viewer to axios
---
app/assets/javascripts/blob/viewer/index.js | 22 +++++++-----------
spec/javascripts/blob/viewer/index_spec.js | 36 ++++++++++++++---------------
2 files changed, 25 insertions(+), 33 deletions(-)
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index 54132e8537b..612f604e725 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -1,5 +1,6 @@
import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils';
+import axios from '../../lib/utils/axios_utils';
export default class BlobViewer {
constructor() {
@@ -127,25 +128,18 @@ export default class BlobViewer {
const viewer = viewerParam;
const url = viewer.getAttribute('data-url');
- return new Promise((resolve, reject) => {
- if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
- resolve(viewer);
- return;
- }
+ if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
+ return Promise.resolve(viewer);
+ }
- viewer.setAttribute('data-loading', 'true');
+ viewer.setAttribute('data-loading', 'true');
- $.ajax({
- url,
- dataType: 'JSON',
- })
- .fail(reject)
- .done((data) => {
+ return axios.get(url)
+ .then(({ data }) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
- resolve(viewer);
+ return viewer;
});
- });
}
}
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index cfa6650d85f..892411a6a40 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -1,28 +1,35 @@
/* eslint-disable no-new */
+import MockAdapter from 'axios-mock-adapter';
import BlobViewer from '~/blob/viewer/index';
+import axios from '~/lib/utils/axios_utils';
describe('Blob viewer', () => {
let blob;
+ let mock;
+
preloadFixtures('snippets/show.html.raw');
beforeEach(() => {
+ mock = new MockAdapter(axios);
+
loadFixtures('snippets/show.html.raw');
$('#modal-upload-blob').remove();
blob = new BlobViewer();
- spyOn($, 'ajax').and.callFake(() => {
- const d = $.Deferred();
-
- d.resolve({
- html: 'testing
',
- });
+ mock.onGet('http://test.host/snippets/1.json?viewer=rich').reply(200, {
+ html: 'testing
',
+ });
- return d.promise();
+ mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, {
+ html: 'testing
',
});
+
+ spyOn(axios, 'get').and.callThrough();
});
afterEach(() => {
+ mock.restore();
location.hash = '';
});
@@ -30,7 +37,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'),
@@ -46,7 +52,6 @@ describe('Blob viewer', () => {
new BlobViewer();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'),
@@ -64,12 +69,8 @@ describe('Blob viewer', () => {
});
asyncClick()
+ .then(() => asyncClick())
.then(() => {
- expect($.ajax).toHaveBeenCalled();
- return asyncClick();
- })
- .then(() => {
- expect($.ajax.calls.count()).toBe(1);
expect(
document.querySelector('.blob-viewer[data-type="simple"]').getAttribute('data-loaded'),
).toBe('true');
@@ -122,7 +123,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
expect(
copyButton.classList.contains('disabled'),
).toBeFalsy();
@@ -135,8 +135,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
-
expect(
copyButton.getAttribute('data-original-title'),
).toBe('Copy source to clipboard');
@@ -171,14 +169,14 @@ describe('Blob viewer', () => {
it('sends AJAX request when switching to simple view', () => {
blob.switchToViewer('simple');
- expect($.ajax).toHaveBeenCalled();
+ expect(axios.get).toHaveBeenCalled();
});
it('does not send AJAX request when switching to rich view', () => {
blob.switchToViewer('simple');
blob.switchToViewer('rich');
- expect($.ajax.calls.count()).toBe(1);
+ expect(axios.get.calls.count()).toBe(1);
});
});
});
--
cgit v1.2.1
From 39e9a17b4b0fd427438521cec108ea62113a02e0 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 09:41:13 +0000
Subject: changed commits.js to axios
---
app/assets/javascripts/commits.js | 34 ++++++++++++++++---------------
spec/javascripts/commits_spec.js | 43 ++++++++++++++++++++++++++++-----------
2 files changed, 49 insertions(+), 28 deletions(-)
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 3a03cbf6b90..4b2f75fffde 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -5,6 +5,7 @@
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
+import axios from './lib/utils/axios_utils';
export default (function () {
const CommitsList = {};
@@ -43,29 +44,30 @@ export default (function () {
CommitsList.filterResults = function () {
const form = $('.commits-search-form');
const search = CommitsList.searchField.val();
- if (search === CommitsList.lastSearch) return;
+ if (search === CommitsList.lastSearch) return Promise.resolve();
const commitsUrl = form.attr('action') + '?' + form.serialize();
CommitsList.content.fadeTo('fast', 0.5);
- return $.ajax({
- type: 'GET',
- url: form.attr('action'),
- data: form.serialize(),
- complete: function () {
- return CommitsList.content.fadeTo('fast', 1.0);
- },
- success: function (data) {
+ const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
+ [obj.name]: obj.value,
+ }), {});
+
+ return axios.get(form.attr('action'), {
+ params,
+ })
+ .then(({ data }) => {
CommitsList.lastSearch = search;
CommitsList.content.html(data.html);
- return history.replaceState({
- page: commitsUrl,
+ CommitsList.content.fadeTo('fast', 1.0);
+
// Change url so if user reload a page - search results are saved
+ history.replaceState({
+ page: commitsUrl,
}, document.title, commitsUrl);
- },
- error: function () {
+ })
+ .catch(() => {
+ CommitsList.content.fadeTo('fast', 1.0);
CommitsList.lastSearch = null;
- },
- dataType: 'json',
- });
+ });
};
// Prepare loaded data.
diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js
index d0176520440..44ec9e4eabf 100644
--- a/spec/javascripts/commits_spec.js
+++ b/spec/javascripts/commits_spec.js
@@ -1,4 +1,6 @@
import 'vendor/jquery.endless-scroll';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import CommitsList from '~/commits';
describe('Commits List', () => {
@@ -43,30 +45,47 @@ describe('Commits List', () => {
describe('on entering input', () => {
let ajaxSpy;
+ let mock;
beforeEach(() => {
CommitsList.init(25);
CommitsList.searchField.val('');
spyOn(history, 'replaceState').and.stub();
- ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => {
- req.success({
- data: 'Result ',
- });
+ mock = new MockAdapter(axios);
+
+ mock.onGet('/h5bp/html5-boilerplate/commits/master').reply(200, {
+ html: 'Result ',
});
+
+ ajaxSpy = spyOn(axios, 'get').and.callThrough();
+ });
+
+ afterEach(() => {
+ mock.restore();
});
- it('should save the last search string', () => {
+ it('should save the last search string', (done) => {
CommitsList.searchField.val('GitLab');
- CommitsList.filterResults();
- expect(ajaxSpy).toHaveBeenCalled();
- expect(CommitsList.lastSearch).toEqual('GitLab');
+ CommitsList.filterResults()
+ .then(() => {
+ expect(ajaxSpy).toHaveBeenCalled();
+ expect(CommitsList.lastSearch).toEqual('GitLab');
+
+ done();
+ })
+ .catch(done.fail);
});
- it('should not make ajax call if the input does not change', () => {
- CommitsList.filterResults();
- expect(ajaxSpy).not.toHaveBeenCalled();
- expect(CommitsList.lastSearch).toEqual('');
+ it('should not make ajax call if the input does not change', (done) => {
+ CommitsList.filterResults()
+ .then(() => {
+ expect(ajaxSpy).not.toHaveBeenCalled();
+ expect(CommitsList.lastSearch).toEqual('');
+
+ done();
+ })
+ .catch(done.fail);
});
});
});
--
cgit v1.2.1
From 3bf550bf24067972a7ddaf192a88f6e349be838c Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 10:56:40 +0000
Subject: fixed unhandled promise rejection
---
spec/javascripts/repo/components/new_dropdown/upload_spec.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
index 788c08e5279..667112ab21a 100644
--- a/spec/javascripts/repo/components/new_dropdown/upload_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
@@ -17,8 +17,10 @@ describe('new dropdown upload', () => {
}));
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: {
- id: '123branch',
+ data: {
+ commit: {
+ id: '123branch',
+ },
},
}));
--
cgit v1.2.1
From 94c8bd9f47df5762cd6e9603dbbaf38efa47a2e7 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 11:29:37 +0000
Subject: fixed more unhandled promise rejections
---
spec/javascripts/repo/components/new_dropdown/modal_spec.js | 6 ++++--
spec/javascripts/repo/stores/actions_spec.js | 12 +++++++++---
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
index 233cca06ed0..8bbc3100357 100644
--- a/spec/javascripts/repo/components/new_dropdown/modal_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
@@ -18,8 +18,10 @@ describe('new file modal component', () => {
}));
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: {
- id: '123branch',
+ data: {
+ commit: {
+ id: '123branch',
+ },
},
}));
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
index 73a8dcd7a89..f678967b092 100644
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ b/spec/javascripts/repo/stores/actions_spec.js
@@ -178,7 +178,9 @@ describe('Multi-file store actions', () => {
it('calls service', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: { id: '123' },
+ data: {
+ commit: { id: '123' },
+ },
}));
store.dispatch('checkCommitStatus')
@@ -192,7 +194,9 @@ describe('Multi-file store actions', () => {
it('returns true if current ref does not equal returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: { id: '123' },
+ data: {
+ commit: { id: '123' },
+ },
}));
store.dispatch('checkCommitStatus')
@@ -206,7 +210,9 @@ describe('Multi-file store actions', () => {
it('returns false if current ref equals returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: { id: '1' },
+ data: {
+ commit: { id: '1' },
+ },
}));
store.dispatch('checkCommitStatus')
--
cgit v1.2.1
From f4a15ac0a310e66c5643ae0f4295ea8fb2c2f24d Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 26 Jan 2018 12:27:58 +0000
Subject: fixed failing IDE test
---
app/assets/javascripts/ide/stores/actions.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 69bd18e46b8..d007d0ae78f 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -71,7 +71,7 @@ export const setResizingStatus = ({ commit }, resizing) => {
export const checkCommitStatus = ({ state }) =>
service
.getBranchData(state.currentProjectId, state.currentBranchId)
- .then((data) => {
+ .then(({ data }) => {
const { id } = data.commit;
const selectedBranch =
state.projects[state.currentProjectId].branches[state.currentBranchId];
--
cgit v1.2.1
From e0bc7e5f9cda721fff329f3dff31f6fad611762d Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Mon, 29 Jan 2018 10:12:32 +0000
Subject: backported changed from EE
---
app/assets/javascripts/api.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index bd2842c90f5..acfee3fe13e 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -23,7 +23,11 @@ const Api = {
const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId);
return axios.get(url)
- .then(({ data }) => callback(data));
+ .then(({ data }) => {
+ callback(data);
+
+ return data;
+ });
},
// Return groups list. Filtered by query
--
cgit v1.2.1
From 06cd08ebadd3fd3571be79df6f50cffdf6247371 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Mon, 29 Jan 2018 11:42:27 +0000
Subject: more EE backports
---
app/assets/javascripts/api.js | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index acfee3fe13e..bd2b9cc2976 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -39,7 +39,11 @@ const Api = {
per_page: 20,
}, options),
})
- .then(({ data }) => callback(data));
+ .then(({ data }) => {
+ callback(data);
+
+ return data;
+ });
},
// Return namespaces list. Filtered by query
@@ -70,7 +74,11 @@ const Api = {
return axios.get(url, {
params: Object.assign(defaults, options),
})
- .then(({ data }) => callback(data));
+ .then(({ data }) => {
+ callback(data);
+
+ return data;
+ });
},
// Return single project
--
cgit v1.2.1
From 3058334b99d3441073eb003f71765442d996dcb9 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Mon, 29 Jan 2018 15:13:20 +0000
Subject: EE backport for callback being optional
---
app/assets/javascripts/api.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index bd2b9cc2976..2fddd6b0ed4 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -59,7 +59,7 @@ const Api = {
},
// Return projects list. Filtered by query
- projects(query, options, callback) {
+ projects(query, options, callback = $.noop) {
const url = Api.buildUrl(Api.projectsPath);
const defaults = {
search: query,
--
cgit v1.2.1
From b367cc0ca28de6b4e735939b1981668ff21b8ecf Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Tue, 30 Jan 2018 09:10:58 +0000
Subject: potential fix for tests crashing use underscore noop instead of
jquery
---
app/assets/javascripts/api.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 2fddd6b0ed4..1f34c6b50c2 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,8 +1,9 @@
+import _ from 'underscore';
import axios from './lib/utils/axios_utils';
const Api = {
groupsPath: '/api/:version/groups.json',
- groupPath: '/api/:version/groups/:id.json',
+ groupPath: '/api/:version/groups/:id',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
@@ -59,7 +60,7 @@ const Api = {
},
// Return projects list. Filtered by query
- projects(query, options, callback = $.noop) {
+ projects(query, options, callback = _.noop) {
const url = Api.buildUrl(Api.projectsPath);
const defaults = {
search: query,
--
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 f9acd8056984eb9261c7b883bded076b8a26f2a0 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Tue, 30 Jan 2018 10:52:38 +0000
Subject: fixed failing specs
---
spec/features/projects/members/share_with_group_spec.rb | 2 +-
spec/javascripts/api_spec.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/spec/features/projects/members/share_with_group_spec.rb b/spec/features/projects/members/share_with_group_spec.rb
index 3198798306c..4cf48098401 100644
--- a/spec/features/projects/members/share_with_group_spec.rb
+++ b/spec/features/projects/members/share_with_group_spec.rb
@@ -122,7 +122,7 @@ feature 'Project > Members > Share with Group', :js do
select2 group.id, from: '#link_group_id'
fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d')
- page.find('body').click
+ click_on 'share-with-group-tab'
find('.btn-create').click
end
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 2e45c5e2712..cf3a76d0d2e 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -37,7 +37,7 @@ describe('Api', () => {
describe('group', () => {
it('fetches a group', (done) => {
const groupId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}.json`;
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
mock.onGet(expectedUrl).reply(200, {
name: 'test',
});
--
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 10fb904d636ab49b87e2deb756f64fc26d2eacec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kim=20=22BKC=22=20Carlb=C3=A4cker?=
Date: Wed, 17 Jan 2018 06:43:30 +0100
Subject: Migrate Git::Repository#write_ref to Gitaly
---
app/models/repository.rb | 2 ++
lib/gitlab/git/repository.rb | 20 ++++++++++++++++----
lib/gitlab/gitaly_client/repository_service.rb | 16 ++++++++++++++++
3 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 5b06dc5a39b..2146e5a74f2 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -255,6 +255,8 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
+ rescue Gitlab::Git::CommandError => ex
+ Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
end
def kept_around?(sha)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 3a7930154e5..e65719e7f92 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1105,10 +1105,14 @@ module Gitlab
end
def write_ref(ref_path, ref, old_ref: nil, shell: true)
- if shell
- shell_write_ref(ref_path, ref, old_ref)
- else
- rugged_write_ref(ref_path, ref)
+ ref_path = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{ref_path}" unless ref_path.start_with?("refs/") || ref_path == "HEAD"
+
+ gitaly_migrate(:write_ref) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.write_ref(ref_path, ref, old_ref, shell)
+ else
+ local_write_ref(ref_path, ref, old_ref: old_ref, shell: shell)
+ end
end
end
@@ -1419,6 +1423,14 @@ module Gitlab
private
+ def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
+ if shell
+ shell_write_ref(ref_path, ref, old_ref)
+ else
+ rugged_write_ref(ref_path, ref)
+ end
+ end
+
def shell_write_ref(ref_path, ref, old_ref)
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index b0dbaf11598..7adf32af209 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -203,6 +203,22 @@ module Gitlab
timeout: GitalyClient.default_timeout
)
end
+
+ def write_ref(ref_path, ref, old_ref, shell)
+ request = Gitaly::WriteRefRequest.new(
+ repository: @gitaly_repo,
+ ref: ref_path.b,
+ revision: ref.b,
+ shell: shell
+ )
+ request.old_revision = old_ref.b unless old_ref.nil?
+
+ response = GitalyClient.call(@storage, :repository_service, :write_ref, request)
+
+ raise Gitlab::Git::CommandError, encode!(response.error) if response.error.present?
+
+ true
+ end
end
end
end
--
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
From b02a6bed85000db63189b68fe8ce6154c283bc39 Mon Sep 17 00:00:00 2001
From: Jan Provaznik
Date: Mon, 29 Jan 2018 16:22:58 +0100
Subject: Make pagination optional for issuables
On epics roadmap page we list all epics in the given time frame
without pagination (at least for the first iteration), in this
case it would be nice to use the existing issuables index logic
except pagination (see MR gitlab-ee!4281). For this reason this
patch allows to easily disable pagination.
Related gitlab-ee!4281
---
app/controllers/concerns/issuable_collections.rb | 20 +++++++++++++++-----
spec/controllers/projects/issues_controller_spec.rb | 12 ++++++++++++
2 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index b25e753a5ad..2fa0f98e344 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -12,11 +12,9 @@ module IssuableCollections
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_issuables_index
- @issuables = issuables_collection
- @issuables = @issuables.page(params[:page])
- @issuable_meta_data = issuable_meta_data(@issuables, collection_type)
- @total_pages = issuable_page_count
+ @issuables = issuables_collection
+ set_pagination
return if redirect_out_of_range(@total_pages)
if params[:label_name].present?
@@ -35,14 +33,26 @@ module IssuableCollections
@users.push(author) if author
end
end
+
+ def set_pagination
+ return if pagination_disabled?
+
+ @issuables = @issuables.page(params[:page])
+ @issuable_meta_data = issuable_meta_data(@issuables, collection_type)
+ @total_pages = issuable_page_count
+ end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ def pagination_disabled?
+ false
+ end
+
def issuables_collection
finder.execute.preload(preload_for_collection)
end
def redirect_out_of_range(total_pages)
- return false if total_pages.zero?
+ return false if total_pages.nil? || total_pages.zero?
out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 4a2998b4ccd..9656e7f7e74 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -102,6 +102,18 @@ describe Projects::IssuesController do
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
end
+
+ it 'does not use pagination if disabled' do
+ allow(controller).to receive(:pagination_disabled?).and_return(true)
+
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ page: (last_page + 1).to_param
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:issues).size).to eq(2)
+ end
end
end
--
cgit v1.2.1
From 7c659e5b533d4b2ee73a3ff0a013cac9366f6ace Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Tue, 30 Jan 2018 14:53:28 +0000
Subject: Converted issue.js to axios
---
app/assets/javascripts/issue.js | 40 +++++++++-----------
spec/javascripts/issue_spec.js | 82 +++++++++++++++++++++++++++++------------
2 files changed, 76 insertions(+), 46 deletions(-)
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 411c820cc43..ff65ea99e9a 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,7 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
import 'vendor/jquery.waitforimages';
+import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility';
-import Flash from './flash';
+import flash from './flash';
import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper';
@@ -42,12 +43,8 @@ export default class Issue {
this.disableCloseReopenButton($button);
url = $button.attr('href');
- return $.ajax({
- type: 'PUT',
- url: url
- })
- .fail(() => new Flash(issueFailMessage))
- .done((data) => {
+ return axios.put(url)
+ .then(({ data }) => {
const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');
@@ -74,9 +71,10 @@ export default class Issue {
}
}
} else {
- new Flash(issueFailMessage);
+ flash(issueFailMessage);
}
})
+ .catch(() => flash(issueFailMessage))
.then(() => {
this.disableCloseReopenButton($button, false);
});
@@ -115,24 +113,22 @@ export default class Issue {
static initMergeRequests() {
var $container;
$container = $('#merge-requests');
- return $.getJSON($container.data('url')).fail(function() {
- return new Flash('Failed to load referenced merge requests');
- }).done(function(data) {
- if ('html' in data) {
- return $container.html(data.html);
- }
- });
+ return axios.get($container.data('url'))
+ .then(({ data }) => {
+ if ('html' in data) {
+ $container.html(data.html);
+ }
+ }).catch(() => flash('Failed to load referenced merge requests'));
}
static initRelatedBranches() {
var $container;
$container = $('#related-branches');
- return $.getJSON($container.data('url')).fail(function() {
- return new Flash('Failed to load related branches');
- }).done(function(data) {
- if ('html' in data) {
- return $container.html(data.html);
- }
- });
+ return axios.get($container.data('url'))
+ .then(({ data }) => {
+ if ('html' in data) {
+ $container.html(data.html);
+ }
+ }).catch(() => flash('Failed to load related branches'));
}
}
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 2cd2e63b15d..2da7cede510 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
+import MockAdaptor from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import Issue from '~/issue';
import '~/lib/utils/text_utility';
@@ -88,6 +90,7 @@ describe('Issue', function() {
[true, false].forEach((isIssueInitiallyOpen) => {
describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
const action = isIssueInitiallyOpen ? 'close' : 'reopen';
+ let mock;
function ajaxSpy(req) {
if (req.url === this.$triggeredButton.attr('href')) {
@@ -104,6 +107,18 @@ describe('Issue', function() {
return null;
}
+ function mockCloseButtonResponseSuccess(url, response) {
+ mock.onPut(url).reply(() => {
+ expectNewBranchButtonState(true, false);
+
+ return [200, response];
+ });
+ }
+
+ function mockCloseButtonResponseError(url) {
+ mock.onPut(url).networkError();
+ }
+
beforeEach(function() {
if (isIssueInitiallyOpen) {
loadFixtures('issues/open-issue.html.raw');
@@ -123,68 +138,87 @@ describe('Issue', function() {
this.issueStateDeferred = new jQuery.Deferred();
this.canCreateBranchDeferred = new jQuery.Deferred();
+ mock = new MockAdaptor(axios);
+
spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this));
});
- it(`${action}s the issue`, function() {
- this.$triggeredButton.trigger('click');
- this.issueStateDeferred.resolve({
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it(`${action}s the issue`, function(done) {
+ mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
id: 34
});
+
+ this.$triggeredButton.trigger('click');
this.canCreateBranchDeferred.resolve({
can_create_branch: !isIssueInitiallyOpen
});
- expectIssueState(!isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
- expectNewBranchButtonState(false, !isIssueInitiallyOpen);
+ setTimeout(() => {
+ expectIssueState(!isIssueInitiallyOpen);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
+ expectNewBranchButtonState(false, !isIssueInitiallyOpen);
+
+ done();
+ });
});
- it(`fails to ${action} the issue if saved:false`, function() {
- this.$triggeredButton.trigger('click');
- this.issueStateDeferred.resolve({
+ it(`fails to ${action} the issue if saved:false`, function(done) {
+ mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
saved: false
});
+ this.$triggeredButton.trigger('click');
this.canCreateBranchDeferred.resolve({
can_create_branch: isIssueInitiallyOpen
});
- expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expectErrorMessage();
- expect(this.$projectIssuesCounter.text()).toBe('1,001');
- expectNewBranchButtonState(false, isIssueInitiallyOpen);
+ setTimeout(() => {
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
+
+ done();
+ });
});
- it(`fails to ${action} the issue if HTTP error occurs`, function() {
+ it(`fails to ${action} the issue if HTTP error occurs`, function(done) {
+ mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
this.$triggeredButton.trigger('click');
- this.issueStateDeferred.reject();
this.canCreateBranchDeferred.resolve({
can_create_branch: isIssueInitiallyOpen
});
- expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expectErrorMessage();
- expect(this.$projectIssuesCounter.text()).toBe('1,001');
- expectNewBranchButtonState(false, isIssueInitiallyOpen);
+ setTimeout(() => {
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
+
+ done();
+ });
});
it('disables the new branch button if Ajax call fails', function() {
+ mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
this.$triggeredButton.trigger('click');
- this.issueStateDeferred.reject();
this.canCreateBranchDeferred.reject();
expectNewBranchButtonState(false, false);
});
it('does not trigger Ajax call if new branch button is missing', function() {
+ mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
Issue.$btnNewBranch = $();
this.canCreateBranchDeferred = null;
this.$triggeredButton.trigger('click');
- this.issueStateDeferred.reject();
});
});
});
--
cgit v1.2.1
From 382421a9bed1ef0fd1e97f6dcec8a104342ae1d8 Mon Sep 17 00:00:00 2001
From: Sean McGivern
Date: Tue, 30 Jan 2018 15:17:44 +0000
Subject: Fix truncated_diff_lines for legacy notes without a line
It appears that some legacy diff notes on GitLab.com have invalid line codes,
which means truncated_diff_lines won't work. This is just a band-aid solution,
as neither the diff nor the discussion will display correctly for these notes,
but they won't cause a 500 error.
---
app/models/concerns/discussion_on_diff.rb | 2 ++
...g-merge-request-undefined-method-index-for-nil-nilclass.yml | 5 +++++
spec/models/concerns/discussion_on_diff_spec.rb | 10 ++++++++++
3 files changed, 17 insertions(+)
create mode 100644 changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index db9770fabf4..8b3c55387b3 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -37,6 +37,8 @@ module DiscussionOnDiff
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines(highlight: true)
+ return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote)
+
lines = highlight ? highlighted_diff_lines : diff_lines
initial_line_index = [diff_line.index - NUMBER_OF_TRUNCATED_DIFF_LINES + 1, 0].max
diff --git a/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml b/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml
new file mode 100644
index 00000000000..64340ab08cd
--- /dev/null
+++ b/changelogs/unreleased/42160-error-500-loading-merge-request-undefined-method-index-for-nil-nilclass.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error when loading a merge request with an invalid comment
+merge_request: 16795
+author:
+type: fixed
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 2322eb206fb..30572ce9332 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -20,6 +20,16 @@ describe DiscussionOnDiff do
expect(truncated_lines).not_to include(be_meta)
end
end
+
+ context "when the diff line does not exist on a legacy diff note" do
+ it "returns an empty array" do
+ legacy_note = LegacyDiffNote.new
+
+ allow(subject).to receive(:first_note).and_return(legacy_note)
+
+ expect(truncated_lines).to eq([])
+ end
+ end
end
describe '#line_code_in_diffs' do
--
cgit v1.2.1
From 405d8b456f65c7165e8cca83ab0be4c9db6cadd2 Mon Sep 17 00:00:00 2001
From: Jose Ivan Vargas
Date: Thu, 18 Jan 2018 16:22:20 -0600
Subject: Added pipeline status to the files view
---
.../commit_pipeline_status_component.vue | 95 ++++++++++++++++++++++
.../tree/services/commit_pipeline_service.js | 11 +++
.../javascripts/pages/projects/tree/show/index.js | 18 ++++
app/assets/stylesheets/pages/commits.scss | 9 ++
app/helpers/ci_status_helper.rb | 11 ---
app/views/projects/commits/_commit.html.haml | 7 +-
.../commit_pipeline_status_component_spec.js | 68 ++++++++++++++++
7 files changed, 202 insertions(+), 17 deletions(-)
create mode 100644 app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue
create mode 100644 app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js
create mode 100644 spec/javascripts/commit/commit_pipeline_status_component_spec.js
diff --git a/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue
new file mode 100644
index 00000000000..2bd1e8e3266
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js b/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js
new file mode 100644
index 00000000000..4b4189bc2de
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js
@@ -0,0 +1,11 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default class CommitPipelineService {
+ constructor(endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ fetchData() {
+ return axios.get(this.endpoint);
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index 28a0160f47d..7181d4dfd47 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,8 +1,10 @@
+import Vue from 'vue';
import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../shortcuts_navigation';
import BlobViewer from '../../../../blob/viewer';
import NewCommitForm from '../../../../new_commit_form';
import { ajaxGet } from '../../../../lib/utils/common_utils';
+import commitPipelineStatus from '../components/commit_pipeline_status_component.vue';
export default () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
@@ -11,5 +13,21 @@ export default () => {
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
$('#tree-slider').waitForImages(() =>
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
+
+ const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#commit-pipeline-status',
+ components: {
+ commitPipelineStatus,
+ },
+ render(createElement) {
+ return createElement('commit-pipeline-status', {
+ props: {
+ endpoint: commitPipelineStatusEl.dataset.endpoint,
+ },
+ });
+ },
+ });
};
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index aeaa33bd3bd..adfd72556b8 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -195,6 +195,10 @@
.commit-actions {
@media (min-width: $screen-sm-min) {
font-size: 0;
+
+ span {
+ font-size: 6px;
+ }
}
.ci-status-link {
@@ -219,6 +223,11 @@
font-size: 14px;
font-weight: $gl-font-weight-bold;
}
+
+ .ci-status-icon {
+ position: relative;
+ top: 3px;
+ }
}
.commit,
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 636316da80a..e8365f1da1e 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -103,17 +103,6 @@ module CiStatusHelper
tooltip_placement: tooltip_placement)
end
- def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
- project = commit.project
- path = pipelines_project_commit_path(project, commit)
-
- render_status_with_link(
- 'commit',
- commit.status(ref),
- path,
- tooltip_placement: tooltip_placement)
- end
-
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
project = pipeline.project
path = project_pipeline_path(project, pipeline)
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index d66066a6d0b..436e1739180 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -26,9 +26,6 @@
%span.commit-row-message.visible-xs-inline
·
= commit.short_id
- - if commit.status(ref)
- .visible-xs-inline
- = render_commit_status(commit, ref: ref)
- if commit.description?
%button.text-expander.hidden-xs.js-toggle-button{ type: "button" } ...
@@ -48,9 +45,7 @@
- else
= render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
- - if commit.status(ref)
- = render_commit_status(commit, ref: ref)
-
+ #commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
= link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
new file mode 100644
index 00000000000..f6fca9e97e5
--- /dev/null
+++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import commitPipelineStatus from '~/pages/projects/tree/components/commit_pipeline_status_component.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Commit pipeline status component', () => {
+ let vm;
+ let component;
+ let mock;
+ const mockCiStatus = {
+ details_path: '/root/hello-world/pipelines/1',
+ favicon: 'canceled.ico',
+ group: 'canceled',
+ has_details: true,
+ icon: 'status_canceled',
+ label: 'canceled',
+ text: 'canceled',
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet('/dummy/endpoint').reply(() => {
+ const res = Promise.resolve([200, {
+ pipelines: [
+ {
+ details: {
+ status: mockCiStatus,
+ },
+ },
+ ],
+ }]);
+ return res;
+ });
+ component = Vue.extend(commitPipelineStatus);
+ });
+
+ afterEach(() => {
+ mock.reset();
+ });
+
+ describe('While polling pipeline data', () => {
+ beforeEach(() => {
+ vm = mountComponent(component, {
+ endpoint: '/dummy/endpoint',
+ });
+ });
+
+ afterEach(() => {
+ vm.poll.stop();
+ vm.$destroy();
+ });
+
+ it('contains a ciStatus when the polling is succesful ', (done) => {
+ setTimeout(() => {
+ expect(vm.ciStatus).toEqual(mockCiStatus);
+ done();
+ }, 1000);
+ });
+
+ it('contains a ci-status icon when polling is succesful', (done) => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
+ done();
+ });
+ });
+ });
+});
--
cgit v1.2.1
From 6042454600d79f1d6fb8e216c78b3e8b619a7a3e Mon Sep 17 00:00:00 2001
From: Jose Ivan Vargas
Date: Fri, 19 Jan 2018 15:09:42 -0600
Subject: Added changelog
---
.../35779-realtime-update-of-pipeline-status-in-files-view.yml | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml
diff --git a/changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml b/changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml
new file mode 100644
index 00000000000..82df00fe631
--- /dev/null
+++ b/changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml
@@ -0,0 +1,5 @@
+---
+title: Add realtime ci status for the repository -> files view
+merge_request: 16523
+author:
+type: added
--
cgit v1.2.1
From bfc2b8a3d2c06c80126365348ce75b3985185e83 Mon Sep 17 00:00:00 2001
From: Jose Ivan Vargas
Date: Mon, 22 Jan 2018 15:57:32 -0600
Subject: Added realtime prop and corrected specs
---
.../javascripts/pages/projects/show/index.js | 19 ++++
.../commit_pipeline_status_component.vue | 95 ------------------
.../tree/services/commit_pipeline_service.js | 11 ---
.../javascripts/pages/projects/tree/show/index.js | 2 +-
.../commit_pipeline_status_component.vue | 106 +++++++++++++++++++++
.../tree/services/commit_pipeline_service.js | 11 +++
features/project/project.feature | 1 +
features/steps/shared/project.rb | 1 +
.../commit_pipeline_status_component_spec.js | 9 +-
9 files changed, 146 insertions(+), 109 deletions(-)
delete mode 100644 app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue
delete mode 100644 app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js
create mode 100644 app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
create mode 100644 app/assets/javascripts/projects/tree/services/commit_pipeline_service.js
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 55154cdddcb..0213b46eb7c 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,3 +1,4 @@
+import Vue from 'vue';
import ShortcutsNavigation from '~/shortcuts_navigation';
import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout';
@@ -5,6 +6,7 @@ import TreeView from '~/tree';
import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
+import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
@@ -24,4 +26,21 @@ export default () => {
$('#tree-slider').waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
+
+ const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#commit-pipeline-status',
+ components: {
+ commitPipelineStatus,
+ },
+ render(createElement) {
+ return createElement('commit-pipeline-status', {
+ props: {
+ endpoint: commitPipelineStatusEl.dataset.endpoint,
+ realtime: false,
+ },
+ });
+ },
+ });
};
diff --git a/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue
deleted file mode 100644
index 2bd1e8e3266..00000000000
--- a/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js b/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js
deleted file mode 100644
index 4b4189bc2de..00000000000
--- a/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import axios from '~/lib/utils/axios_utils';
-
-export default class CommitPipelineService {
- constructor(endpoint) {
- this.endpoint = endpoint;
- }
-
- fetchData() {
- return axios.get(this.endpoint);
- }
-}
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index 7181d4dfd47..c0b3f98e66d 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
+import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../shortcuts_navigation';
import BlobViewer from '../../../../blob/viewer';
import NewCommitForm from '../../../../new_commit_form';
import { ajaxGet } from '../../../../lib/utils/common_utils';
-import commitPipelineStatus from '../components/commit_pipeline_status_component.vue';
export default () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
new file mode 100644
index 00000000000..e13acf8555a
--- /dev/null
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/projects/tree/services/commit_pipeline_service.js b/app/assets/javascripts/projects/tree/services/commit_pipeline_service.js
new file mode 100644
index 00000000000..4b4189bc2de
--- /dev/null
+++ b/app/assets/javascripts/projects/tree/services/commit_pipeline_service.js
@@ -0,0 +1,11 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default class CommitPipelineService {
+ constructor(endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ fetchData() {
+ return axios.get(this.endpoint);
+ }
+}
diff --git a/features/project/project.feature b/features/project/project.feature
index 23817ef3ac9..bcd72c5c5a3 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -23,6 +23,7 @@ Feature: Project
And I visit project "Shop" page
Then I should see project "Shop" README
+ @javascript
Scenario: I should see last commit with CI
Given project "Shop" has CI enabled
Given project "Shop" has CI build
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index affbccccdf9..923d54a6545 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -218,6 +218,7 @@ module SharedProject
end
step 'I should see last commit with CI status' do
+ sleep 2
page.within ".blob-commit-info" do
expect(page).to have_content(project.commit.sha[0..6])
expect(page).to have_link("Commit: skipped")
diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
index f6fca9e97e5..2a52097e0d5 100644
--- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js
+++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import commitPipelineStatus from '~/pages/projects/tree/components/commit_pipeline_status_component.vue';
+import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Commit pipeline status component', () => {
@@ -25,7 +25,12 @@ describe('Commit pipeline status component', () => {
pipelines: [
{
details: {
- status: mockCiStatus,
+ stages: [
+ {
+ status: mockCiStatus,
+ title: 'Commit: canceled',
+ },
+ ],
},
},
],
--
cgit v1.2.1
From 449b0ebaf6f3ca65b48f372293117acc9a7e0abc Mon Sep 17 00:00:00 2001
From: Jose Ivan Vargas
Date: Mon, 22 Jan 2018 18:34:21 -0600
Subject: Restored some code, add hidden class
---
app/assets/javascripts/pages/projects/show/index.js | 18 ------------------
.../javascripts/pages/projects/tree/show/index.js | 5 +++++
app/helpers/ci_status_helper.rb | 11 +++++++++++
app/views/projects/commits/_commit.html.haml | 8 +++++++-
4 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 0213b46eb7c..4c42fda16d7 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,4 +1,3 @@
-import Vue from 'vue';
import ShortcutsNavigation from '~/shortcuts_navigation';
import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout';
@@ -26,21 +25,4 @@ export default () => {
$('#tree-slider').waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
-
- const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
- // eslint-disable-next-line no-new
- new Vue({
- el: '#commit-pipeline-status',
- components: {
- commitPipelineStatus,
- },
- render(createElement) {
- return createElement('commit-pipeline-status', {
- props: {
- endpoint: commitPipelineStatusEl.dataset.endpoint,
- realtime: false,
- },
- });
- },
- });
};
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index c0b3f98e66d..f14c3f86687 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -15,6 +15,11 @@ export default () => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
+ const $statusLink = $('.ci-status-link');
+ if ($statusLink.length > 0) {
+ $statusLink.remove();
+ }
+ commitPipelineStatusEl.classList.remove('hidden');
// eslint-disable-next-line no-new
new Vue({
el: '#commit-pipeline-status',
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index e8365f1da1e..636316da80a 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -103,6 +103,17 @@ module CiStatusHelper
tooltip_placement: tooltip_placement)
end
+ def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
+ project = commit.project
+ path = pipelines_project_commit_path(project, commit)
+
+ render_status_with_link(
+ 'commit',
+ commit.status(ref),
+ path,
+ tooltip_placement: tooltip_placement)
+ end
+
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
project = pipeline.project
path = project_pipeline_path(project, pipeline)
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 436e1739180..c94e10947e6 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -26,6 +26,9 @@
%span.commit-row-message.visible-xs-inline
·
= commit.short_id
+ - if commit.status(ref)
+ .visible-xs-inline
+ = render_commit_status(commit, ref: ref)
- if commit.description?
%button.text-expander.hidden-xs.js-toggle-button{ type: "button" } ...
@@ -45,7 +48,10 @@
- else
= render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
- #commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
+ - if commit.status(ref)
+ = render_commit_status(commit, ref: ref)
+
+ #commit-pipeline-status.hidden{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
= link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
--
cgit v1.2.1
From 1ab85b9696646bbd5aa8b0da4db35108b36e5d63 Mon Sep 17 00:00:00 2001
From: Tiago Botelho
Date: Wed, 24 Jan 2018 13:52:31 +0000
Subject: Only new users get marked as external
---
.../unreleased/21554-mark-new-user-as-external.yml | 5 ++++
lib/gitlab/o_auth/user.rb | 2 +-
spec/lib/gitlab/o_auth/user_spec.rb | 30 +++++++++++++++-------
3 files changed, 27 insertions(+), 10 deletions(-)
create mode 100644 changelogs/unreleased/21554-mark-new-user-as-external.yml
diff --git a/changelogs/unreleased/21554-mark-new-user-as-external.yml b/changelogs/unreleased/21554-mark-new-user-as-external.yml
new file mode 100644
index 00000000000..fb0826fc176
--- /dev/null
+++ b/changelogs/unreleased/21554-mark-new-user-as-external.yml
@@ -0,0 +1,5 @@
+---
+title: Login via OAuth now only marks new users as external
+merge_request: 16672
+author:
+type: fixed
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index fff9360ea27..e40a001d20c 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -55,7 +55,7 @@ module Gitlab
user ||= find_or_build_ldap_user if auto_link_ldap_user?
user ||= build_new_user if signup_enabled?
- user.external = true if external_provider? && user
+ user.external = true if external_provider? && user&.new_record?
user
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 45fff4c5787..03e0a9e2a03 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -44,6 +44,18 @@ describe Gitlab::OAuth::User do
let(:provider) { 'twitter' }
+ describe 'when account exists on server' do
+ it 'does not mark the user as external' do
+ create(:omniauth_user, extern_uid: 'my-uid', provider: provider)
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
+
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.external).to be_falsey
+ end
+ end
+
describe 'signup' do
context 'when signup is disabled' do
before do
@@ -51,7 +63,7 @@ describe Gitlab::OAuth::User do
end
it 'creates the user' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider])
oauth_user.save
@@ -65,7 +77,7 @@ describe Gitlab::OAuth::User do
end
it 'creates and confirms the user anyway' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider])
oauth_user.save
@@ -75,7 +87,7 @@ describe Gitlab::OAuth::User do
end
it 'marks user as having password_automatically_set' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
oauth_user.save
@@ -86,7 +98,7 @@ describe Gitlab::OAuth::User do
shared_examples 'to verify compliance with allow_single_sign_on' do
context 'provider is marked as external' do
it 'marks user as external' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
oauth_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
@@ -95,8 +107,8 @@ describe Gitlab::OAuth::User do
context 'provider was external, now has been removed' do
it 'does not mark external user as internal' do
- create(:omniauth_user, extern_uid: 'my-uid', provider: 'twitter', external: true)
- stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['facebook'])
+ create(:omniauth_user, extern_uid: 'my-uid', provider: provider, external: true)
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: ['facebook'])
oauth_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
@@ -118,7 +130,7 @@ describe Gitlab::OAuth::User do
context 'with new allow_single_sign_on enabled syntax' do
before do
- stub_omniauth_config(allow_single_sign_on: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider])
end
it "creates a user from Omniauth" do
@@ -127,7 +139,7 @@ describe Gitlab::OAuth::User do
expect(gl_user).to be_valid
identity = gl_user.identities.first
expect(identity.extern_uid).to eql uid
- expect(identity.provider).to eql 'twitter'
+ expect(identity.provider).to eql provider
end
end
@@ -142,7 +154,7 @@ describe Gitlab::OAuth::User do
expect(gl_user).to be_valid
identity = gl_user.identities.first
expect(identity.extern_uid).to eql uid
- expect(identity.provider).to eql 'twitter'
+ expect(identity.provider).to eql provider
end
end
--
cgit v1.2.1
From a4fdb47f52f53293fecb434e77ce378a1cd917fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Tue, 30 Jan 2018 16:02:24 +0100
Subject: Speed-up the click on 'New subgroup' if the 'No results' text is
shown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
qa/qa/page/group/show.rb | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index f24859f2b1d..00883bbe87b 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -13,6 +13,10 @@ module QA
element :new_subgroup_button, /%input.*\.js-new-group-child/
end
+ view 'app/assets/javascripts/groups/constants.js' do
+ element :no_result_text, 'Sorry, no groups or projects matched your search'
+ end
+
def go_to_subgroup(name)
click_link name
end
@@ -24,7 +28,11 @@ module QA
def has_subgroup?(name)
filter_by_name(name)
- page.has_link?(name)
+ wait(reload: false) do
+ return false if page.has_content?('Sorry, no groups or projects matched your search')
+
+ page.has_link?(name)
+ end
end
def go_to_new_subgroup
--
cgit v1.2.1
From fa9d47f221fff4d75e3c50670aeeac7a3d675f7a Mon Sep 17 00:00:00 2001
From: Ahmad Sherif
Date: Thu, 25 Jan 2018 16:24:11 +0100
Subject: Migrate fetching wiki page formatted content to Gitaly
Closes gitaly#958
---
GITALY_SERVER_VERSION | 2 +-
Gemfile | 2 +-
Gemfile.lock | 4 ++--
app/models/project_wiki.rb | 6 ++++++
app/models/wiki_page.rb | 5 ++++-
lib/gitlab/git/wiki.rb | 14 ++++++++++++++
lib/gitlab/gitaly_client/wiki_service.rb | 12 ++++++++++++
spec/models/wiki_page_spec.rb | 20 +++++++++++++++-----
8 files changed, 55 insertions(+), 10 deletions(-)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index b7c0622b4f4..c52842c467c 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.74.0
+0.75.0
diff --git a/Gemfile b/Gemfile
index ee576c53fe9..a05c3b5ab60 100644
--- a/Gemfile
+++ b/Gemfile
@@ -406,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.78.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.82.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 5532888d179..75ad8dd5f0a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.78.0)
+ gitaly-proto (0.82.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.78.0)
+ gitaly-proto (~> 0.82.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index a0af749a93f..459d1673125 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -124,6 +124,12 @@ class ProjectWiki
update_project_activity
end
+ def page_formatted_data(page)
+ page_title, page_dir = page_title_and_dir(page.title)
+
+ wiki.page_formatted_data(title: page_title, dir: page_dir, version: page.version)
+ end
+
def page_title_and_dir(title)
title_array = title.split("/")
title = title_array.pop
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index bdfef677ef3..e6254183baf 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -107,7 +107,10 @@ class WikiPage
# The processed/formatted content of this page.
def formatted_content
- @attributes[:formatted_content] ||= @page&.formatted_data
+ # Assuming @page exists, nil formatted_data means we didn't load it
+ # before hand (i.e. page was fetched by Gitaly), so we fetch it separately.
+ # If the page was fetched by Gollum, formatted_data would've been a String.
+ @attributes[:formatted_content] ||= @page&.formatted_data || @wiki.page_formatted_data(@page)
end
# The markup format for the page.
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index d4a53d32c28..ccdb8975342 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -117,6 +117,20 @@ module Gitlab
page.url_path
end
+ def page_formatted_data(title:, dir: nil, version: nil)
+ version = version&.id
+
+ @repository.gitaly_migrate(:wiki_page_formatted_data) do |is_enabled|
+ if is_enabled
+ gitaly_wiki_client.get_formatted_data(title: title, dir: dir, version: version)
+ else
+ # We don't use #page because if wiki_find_page feature is enabled, we would
+ # get a page without formatted_data.
+ gollum_find_page(title: title, dir: dir, version: version)&.formatted_data
+ end
+ end
+ end
+
private
# options:
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 5c5b170a3e0..8e87a8cc36f 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -127,6 +127,18 @@ module Gitlab
wiki_file
end
+ def get_formatted_data(title:, dir: nil, version: nil)
+ request = Gitaly::WikiGetFormattedDataRequest.new(
+ repository: @gitaly_repo,
+ title: encode_binary(title),
+ revision: encode_binary(version),
+ directory: encode_binary(dir)
+ )
+
+ response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_formatted_data, request)
+ response.reduce("") { |memo, msg| memo << msg.data }
+ end
+
private
# If a block is given and the yielded value is true, iteration will be
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index cc9d79da708..9840afe6c4e 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -387,13 +387,23 @@ describe WikiPage do
end
describe '#formatted_content' do
- it 'returns processed content of the page', :disable_gitaly do
- subject.create({ title: "RDoc", content: "*bold*", format: "rdoc" })
- page = wiki.find_page('RDoc')
+ shared_examples 'fetching page formatted content' do
+ it 'returns processed content of the page' do
+ subject.create({ title: "RDoc", content: "*bold*", format: "rdoc" })
+ page = wiki.find_page('RDoc')
- expect(page.formatted_content).to eq("\nbold
\n")
+ expect(page.formatted_content).to eq("\nbold
\n")
- destroy_page('RDoc')
+ destroy_page('RDoc')
+ end
+ end
+
+ context 'when Gitaly wiki_page_formatted_data is enabled' do
+ it_behaves_like 'fetching page formatted content'
+ end
+
+ context 'when Gitaly wiki_page_formatted_data is disabled', :disable_gitaly do
+ it_behaves_like 'fetching page formatted content'
end
end
--
cgit v1.2.1
From 5c2fe2d3d75615a263977e2988b3e37aa0e9895a Mon Sep 17 00:00:00 2001
From: "Jacob Vosmaer (GitLab)"
Date: Tue, 30 Jan 2018 16:21:55 +0000
Subject: Decouple more of Repository from Rugged
---
app/models/repository.rb | 67 ++-----------------------
lib/gitlab/git/diff.rb | 2 +-
lib/gitlab/git/operation_service.rb | 5 +-
lib/gitlab/git/repository.rb | 89 +++++++++++++++++++++++++++-------
scripts/lint-rugged | 1 +
spec/lib/gitlab/git/repository_spec.rb | 25 ++++++++++
6 files changed, 107 insertions(+), 82 deletions(-)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 5b06dc5a39b..872d4468ac8 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -491,7 +491,7 @@ class Repository
raw_repository.root_ref
else
# When the repo does not exist we raise this error so no data is cached.
- raise Rugged::ReferenceError
+ raise Gitlab::Git::Repository::NoRepository
end
end
cache_method :root_ref
@@ -525,11 +525,7 @@ class Repository
def commit_count_for_ref(ref)
return 0 unless exists?
- begin
- cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
- rescue Rugged::ReferenceError
- 0
- end
+ cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
end
delegate :branch_names, to: :raw_repository
@@ -653,26 +649,14 @@ class Repository
end
def last_commit_for_path(sha, path)
- raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
- if is_enabled
- last_commit_for_path_by_gitaly(sha, path)
- else
- last_commit_for_path_by_rugged(sha, path)
- end
- end
+ commit_by(oid: last_commit_id_for_path(sha, path))
end
def last_commit_id_for_path(sha, path)
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
cache.fetch(key) do
- raw_repository.gitaly_migrate(:last_commit_for_path) do |is_enabled|
- if is_enabled
- last_commit_for_path_by_gitaly(sha, path).id
- else
- last_commit_id_for_path_by_shelling_out(sha, path)
- end
- end
+ raw_repository.last_commit_id_for_path(sha, path)
end
end
@@ -800,16 +784,6 @@ class Repository
with_cache_hooks { raw.multi_action(user, **options) }
end
- def can_be_merged?(source_sha, target_branch)
- raw_repository.gitaly_migrate(:can_be_merged) do |is_enabled|
- if is_enabled
- gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
- else
- rugged_can_be_merged?(source_sha, target_branch)
- end
- end
- end
-
def merge(user, source_sha, merge_request, message)
with_cache_hooks do
raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
@@ -882,20 +856,12 @@ class Repository
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
raw_repository.merge_base(first_commit_id, second_commit_id)
- rescue Rugged::ReferenceError
- nil
end
def ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil?
- Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
- if is_enabled
- raw_repository.ancestor?(ancestor_id, descendant_id)
- else
- rugged_is_ancestor?(ancestor_id, descendant_id)
- end
- end
+ raw_repository.ancestor?(ancestor_id, descendant_id)
end
def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil)
@@ -1077,30 +1043,7 @@ class Repository
Gitlab::Metrics.add_event(event, { path: full_path }.merge(tags))
end
- def last_commit_for_path_by_gitaly(sha, path)
- c = raw_repository.gitaly_commit_client.last_commit_for_path(sha, path)
- commit_by(oid: c)
- end
-
- def last_commit_for_path_by_rugged(sha, path)
- sha = last_commit_id_for_path_by_shelling_out(sha, path)
- commit_by(oid: sha)
- end
-
- def last_commit_id_for_path_by_shelling_out(sha, path)
- args = %W(rev-list --max-count=1 #{sha} -- #{path})
- raw_repository.run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip
- end
-
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
end
-
- def gitaly_can_be_merged?(their_commit, our_commit)
- !raw_repository.gitaly_conflicts_client(our_commit, their_commit).conflicts?
- end
-
- def rugged_can_be_merged?(their_commit, our_commit)
- !rugged.merge_commits(our_commit, their_commit).conflicts?
- end
end
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index ca94b4baa59..a203587aec1 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -44,7 +44,7 @@ module Gitlab
# branch1...branch2) From the git documentation:
# "git diff A...B" is equivalent to "git diff
# $(git-merge-base A B) B"
- repo.merge_base_commit(head, base)
+ repo.merge_base(head, base)
end
options ||= {}
diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb
index 3fb0e2eed93..280def182d5 100644
--- a/lib/gitlab/git/operation_service.rb
+++ b/lib/gitlab/git/operation_service.rb
@@ -131,7 +131,10 @@ module Gitlab
oldrev = branch.target
- if oldrev == repository.merge_base(newrev, branch.target)
+ merge_base = repository.merge_base(newrev, branch.target)
+ raise Gitlab::Git::Repository::InvalidRef unless merge_base
+
+ if oldrev == merge_base
oldrev
else
raise Gitlab::Git::CommitError.new('Branch diverged')
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 3a7930154e5..e710ad2940f 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -551,29 +551,34 @@ module Gitlab
end
# Returns the SHA of the most recent common ancestor of +from+ and +to+
- def merge_base_commit(from, to)
+ def merge_base(from, to)
gitaly_migrate(:merge_base) do |is_enabled|
if is_enabled
gitaly_repository_client.find_merge_base(from, to)
else
- rugged.merge_base(from, to)
+ rugged_merge_base(from, to)
end
end
end
- alias_method :merge_base, :merge_base_commit
# Gitaly note: JV: check gitlab-ee before removing this method.
def rugged_is_ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil?
- merge_base_commit(ancestor_id, descendant_id) == ancestor_id
+ rugged_merge_base(ancestor_id, descendant_id) == ancestor_id
rescue Rugged::OdbError
false
end
# Returns true is +from+ is direct ancestor to +to+, otherwise false
def ancestor?(from, to)
- gitaly_commit_client.ancestor?(from, to)
+ Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
+ if is_enabled
+ gitaly_commit_client.ancestor?(from, to)
+ else
+ rugged_is_ancestor?(from, to)
+ end
+ end
end
def merged_branch_names(branch_names = [])
@@ -680,11 +685,7 @@ module Gitlab
if is_enabled
gitaly_commit_client.commit_count(ref)
else
- walker = Rugged::Walker.new(rugged)
- walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
- oid = rugged.rev_parse_oid(ref)
- walker.push(oid)
- walker.count
+ rugged_commit_count(ref)
end
end
end
@@ -1130,13 +1131,6 @@ module Gitlab
target_ref
end
- # Refactoring aid; allows us to copy code from app/models/repository.rb
- def run_git_with_timeout(args, timeout, env: {})
- circuit_breaker.perform do
- popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env)
- end
- end
-
# Refactoring aid; allows us to copy code from app/models/repository.rb
def commit(ref = 'HEAD')
Gitlab::Git::Commit.find(self, ref)
@@ -1417,6 +1411,26 @@ module Gitlab
output
end
+ def can_be_merged?(source_sha, target_branch)
+ gitaly_migrate(:can_be_merged) do |is_enabled|
+ if is_enabled
+ gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
+ else
+ rugged_can_be_merged?(source_sha, target_branch)
+ end
+ end
+ end
+
+ def last_commit_id_for_path(sha, path)
+ gitaly_migrate(:last_commit_for_path) do |is_enabled|
+ if is_enabled
+ last_commit_for_path_by_gitaly(sha, path).id
+ else
+ last_commit_id_for_path_by_shelling_out(sha, path)
+ end
+ end
+ end
+
private
def shell_write_ref(ref_path, ref, old_ref)
@@ -1460,6 +1474,12 @@ module Gitlab
output
end
+ def run_git_with_timeout(args, timeout, env: {})
+ circuit_breaker.perform do
+ popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env)
+ end
+ end
+
def fresh_worktree?(path)
File.exist?(path) && !clean_stuck_worktree(path)
end
@@ -2160,7 +2180,7 @@ module Gitlab
source_sha
end
- rescue Rugged::ReferenceError
+ rescue Rugged::ReferenceError, InvalidRef
raise ArgumentError, 'Invalid merge source'
end
@@ -2257,6 +2277,39 @@ module Gitlab
.commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
.map { |c| commit(c) }
end
+
+ def gitaly_can_be_merged?(their_commit, our_commit)
+ !gitaly_conflicts_client(our_commit, their_commit).conflicts?
+ end
+
+ def rugged_can_be_merged?(their_commit, our_commit)
+ !rugged.merge_commits(our_commit, their_commit).conflicts?
+ end
+
+ def last_commit_for_path_by_gitaly(sha, path)
+ gitaly_commit_client.last_commit_for_path(sha, path)
+ end
+
+ def last_commit_id_for_path_by_shelling_out(sha, path)
+ args = %W(rev-list --max-count=1 #{sha} -- #{path})
+ run_git_with_timeout(args, Gitlab::Git::Popen::FAST_GIT_PROCESS_TIMEOUT).first.strip
+ end
+
+ def rugged_merge_base(from, to)
+ rugged.merge_base(from, to)
+ rescue Rugged::ReferenceError
+ nil
+ end
+
+ def rugged_commit_count(ref)
+ walker = Rugged::Walker.new(rugged)
+ walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
+ oid = rugged.rev_parse_oid(ref)
+ walker.push(oid)
+ walker.count
+ rescue Rugged::ReferenceError
+ 0
+ end
end
end
end
diff --git a/scripts/lint-rugged b/scripts/lint-rugged
index 3f8fcb558e3..03f780f880b 100755
--- a/scripts/lint-rugged
+++ b/scripts/lint-rugged
@@ -21,6 +21,7 @@ ALLOWED = [
].freeze
rugged_lines = IO.popen(%w[git grep -i -n rugged -- app config lib], &:read).lines
+rugged_lines = rugged_lines.select { |l| /^[^:]*\.rb:/ =~ l }
rugged_lines = rugged_lines.reject { |l| l.start_with?(*ALLOWED) }
rugged_lines = rugged_lines.reject do |line|
code, _comment = line.split('# ', 2)
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 935d1df6dad..ac7c0270916 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2,6 +2,7 @@ require "spec_helper"
describe Gitlab::Git::Repository, seed_helper: true do
include Gitlab::EncodingHelper
+ using RSpec::Parameterized::TableSyntax
shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method|
it 'wraps gRPC not found error' do
@@ -442,6 +443,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
shared_examples 'simple commit counting' do
it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) }
+ it { expect(repository.commit_count("does-not-exist")).to eq(0) }
end
context 'when Gitaly commit_count feature is enabled' do
@@ -1032,6 +1034,29 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to eq(17) }
end
+ describe '#merge_base' do
+ shared_examples '#merge_base' do
+ where(:from, :to, :result) do
+ '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil
+ 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil
+ end
+
+ with_them do
+ it { expect(repository.merge_base(from, to)).to eq(result) }
+ end
+ end
+
+ context 'with gitaly' do
+ it_behaves_like '#merge_base'
+ end
+
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '#merge_base'
+ end
+ end
+
describe '#count_commits' do
shared_examples 'extended commit counting' do
context 'with after timestamp' do
--
cgit v1.2.1
From d504118402668872d4acc67c10a40cd03d8f8e99 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Tue, 30 Jan 2018 16:33:23 +0000
Subject: Converted job.js to axios
---
app/assets/javascripts/job.js | 12 +-
spec/javascripts/job_spec.js | 263 ++++++++++++++++++++++--------------------
2 files changed, 144 insertions(+), 131 deletions(-)
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index 9b5092c5e3f..d2cbc9b82b3 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { numberToHumanSize } from './lib/utils/number_utils';
@@ -171,11 +172,12 @@ export default class Job {
}
getBuildTrace() {
- return $.ajax({
- url: `${this.pagePath}/trace.json`,
- data: { state: this.state },
+ return axios.get(`${this.pagePath}/trace.json`, {
+ params: { state: this.state },
})
- .done((log) => {
+ .then((res) => {
+ const log = res.data;
+
setCiStatusFavicon(`${this.pagePath}/status.json`);
if (log.state) {
@@ -217,7 +219,7 @@ export default class Job {
visitUrl(this.pagePath);
}
})
- .fail(() => {
+ .catch(() => {
this.$buildRefreshAnimation.remove();
})
.then(() => {
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index feb341d22e6..c3d8d821ac4 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -1,3 +1,5 @@
+import MockAdaptor from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import '~/lib/utils/datetime_utility';
@@ -6,11 +8,31 @@ import '~/breakpoints';
describe('Job', () => {
const JOB_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`;
+ let mock;
+ let response;
+
+ function waitForPromise() {
+ return new Promise(resolve => requestAnimationFrame(resolve));
+ }
preloadFixtures('builds/build-with-artifacts.html.raw');
beforeEach(() => {
loadFixtures('builds/build-with-artifacts.html.raw');
+
+ spyOn(urlUtils, 'visitUrl');
+
+ mock = new MockAdaptor(axios);
+
+ mock.onGet(new RegExp(`${JOB_URL}/trace.json?(.*)`)).reply(() => {
+ return [200, response];
+ });
+ });
+
+ afterEach(() => {
+ mock.restore();
+
+ response = {};
});
describe('class constructor', () => {
@@ -55,170 +77,159 @@ describe('Job', () => {
});
describe('running build', () => {
- it('updates the build trace on an interval', function () {
- const deferred1 = $.Deferred();
- const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
- spyOn(urlUtils, 'visitUrl');
-
- deferred1.resolve({
+ it('updates the build trace on an interval', function (done) {
+ response = {
html: 'Update',
status: 'running',
state: 'newstate',
append: true,
complete: false,
- });
-
- deferred2.resolve();
-
- deferred3.resolve({
- html: 'More ',
- status: 'running',
- state: 'finalstate',
- append: true,
- complete: true,
- });
+ };
this.job = new Job();
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
- expect(this.job.state).toBe('newstate');
-
- jasmine.clock().tick(4001);
-
- expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
- expect(this.job.state).toBe('finalstate');
+ waitForPromise()
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+ expect(this.job.state).toBe('newstate');
+
+ response = {
+ html: 'More ',
+ status: 'running',
+ state: 'finalstate',
+ append: true,
+ complete: true,
+ };
+ })
+ .then(() => jasmine.clock().tick(4001))
+ .then(waitForPromise)
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
+ expect(this.job.state).toBe('finalstate');
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('replaces the entire build trace', () => {
- const deferred1 = $.Deferred();
- const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
-
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
-
- spyOn(urlUtils, 'visitUrl');
-
- deferred1.resolve({
+ it('replaces the entire build trace', (done) => {
+ response = {
html: 'Update',
status: 'running',
append: false,
complete: false,
- });
-
- deferred2.resolve();
-
- deferred3.resolve({
- html: 'Different ',
- status: 'running',
- append: false,
- });
+ };
this.job = new Job();
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-
- jasmine.clock().tick(4001);
-
- expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
- expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
+ waitForPromise()
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+
+ response = {
+ html: 'Different ',
+ status: 'running',
+ append: false,
+ };
+ })
+ .then(() => jasmine.clock().tick(4001))
+ .then(waitForPromise)
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
+ expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('truncated information', () => {
describe('when size is less than total', () => {
- it('shows information about truncated log', () => {
- spyOn(urlUtils, 'visitUrl');
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
-
- deferred.resolve({
+ it('shows information about truncated log', (done) => {
+ response = {
html: 'Update ',
status: 'success',
append: false,
size: 50,
total: 100,
- });
+ };
this.job = new Job();
- expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ waitForPromise()
+ .then(() => {
+ expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('shows the size in KiB', () => {
+ it('shows the size in KiB', (done) => {
const size = 50;
- spyOn(urlUtils, 'visitUrl');
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ response = {
html: 'Update ',
status: 'success',
append: false,
size,
total: 100,
- });
+ };
this.job = new Job();
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${numberToHumanSize(size)}`);
+ waitForPromise()
+ .then(() => {
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${numberToHumanSize(size)}`);
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('shows incremented size', () => {
- const deferred1 = $.Deferred();
- const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
-
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
-
- spyOn(urlUtils, 'visitUrl');
-
- deferred1.resolve({
+ it('shows incremented size', (done) => {
+ response = {
html: 'Update ',
status: 'success',
append: false,
size: 50,
total: 100,
- });
-
- deferred2.resolve();
+ };
this.job = new Job();
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${numberToHumanSize(50)}`);
-
- jasmine.clock().tick(4001);
-
- deferred3.resolve({
- html: 'Update ',
- status: 'success',
- append: true,
- size: 10,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${numberToHumanSize(60)}`);
+ waitForPromise()
+ .then(() => {
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${numberToHumanSize(50)}`);
+
+ response = {
+ html: 'Update ',
+ status: 'success',
+ append: true,
+ size: 10,
+ total: 100,
+ };
+ })
+ .then(() => jasmine.clock().tick(4001))
+ .then(waitForPromise)
+ .then(() => {
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${numberToHumanSize(60)}`);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('renders the raw link', () => {
- const deferred = $.Deferred();
- spyOn(urlUtils, 'visitUrl');
-
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ response = {
html: 'Update ',
status: 'success',
append: false,
size: 50,
total: 100,
- });
+ };
this.job = new Job();
@@ -229,50 +240,50 @@ describe('Job', () => {
});
describe('when size is equal than total', () => {
- it('does not show the trunctated information', () => {
- const deferred = $.Deferred();
- spyOn(urlUtils, 'visitUrl');
-
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ it('does not show the trunctated information', (done) => {
+ response = {
html: 'Update ',
status: 'success',
append: false,
size: 100,
total: 100,
- });
+ };
this.job = new Job();
- expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ waitForPromise()
+ .then(() => {
+ expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('output trace', () => {
- beforeEach(() => {
- const deferred = $.Deferred();
- spyOn(urlUtils, 'visitUrl');
-
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ beforeEach((done) => {
+ response = {
html: 'Update ',
status: 'success',
append: false,
size: 50,
total: 100,
- });
+ };
this.job = new Job();
+
+ waitForPromise()
+ .then(done)
+ .catch(done.fail);
});
it('should render trace controls', () => {
const controllers = document.querySelector('.controllers');
- expect(controllers.querySelector('.js-raw-link-controller')).toBeDefined();
- expect(controllers.querySelector('.js-erase-link')).toBeDefined();
- expect(controllers.querySelector('.js-scroll-up')).toBeDefined();
- expect(controllers.querySelector('.js-scroll-down')).toBeDefined();
+ expect(controllers.querySelector('.js-raw-link-controller')).not.toBeNull();
+ expect(controllers.querySelector('.js-scroll-up')).not.toBeNull();
+ expect(controllers.querySelector('.js-scroll-down')).not.toBeNull();
});
it('should render received output', () => {
@@ -285,13 +296,13 @@ describe('Job', () => {
describe('getBuildTrace', () => {
it('should request build trace with state parameter', (done) => {
- spyOn(jQuery, 'ajax').and.callThrough();
+ spyOn(axios, 'get').and.callThrough();
// eslint-disable-next-line no-new
new Job();
setTimeout(() => {
- expect(jQuery.ajax).toHaveBeenCalledWith(
- { url: `${JOB_URL}/trace.json`, data: { state: '' } },
+ expect(axios.get).toHaveBeenCalledWith(
+ `${JOB_URL}/trace.json`, { params: { state: '' } },
);
done();
}, 0);
--
cgit v1.2.1
From 05a3479c9efe81c1ced7bba37ec4da37e4a9fcd6 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Tue, 30 Jan 2018 16:40:47 +0000
Subject: Converted labels_select.js to axios
---
app/assets/javascripts/labels_select.js | 170 ++++++++++++++++----------------
1 file changed, 85 insertions(+), 85 deletions(-)
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 664e793fc8e..06b8333db27 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -2,9 +2,12 @@
/* global Issuable */
/* global ListLabel */
import _ from 'underscore';
+import { __ } from './locale';
+import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
+import flash from './flash';
export default class LabelsSelect {
constructor(els, options = {}) {
@@ -82,99 +85,96 @@ export default class LabelsSelect {
}
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
- return $.ajax({
- type: 'PUT',
- url: issueUpdateURL,
- dataType: 'JSON',
- data: data
- }).done(function(data) {
- var labelCount, template, labelTooltipTitle, labelTitles;
- $loading.fadeOut();
- $dropdown.trigger('loaded.gl.dropdown');
- $selectbox.hide();
- data.issueURLSplit = issueURLSplit;
- labelCount = 0;
- if (data.labels.length) {
- template = labelHTMLTemplate(data);
- labelCount = data.labels.length;
- }
- else {
- template = labelNoneHTMLTemplate;
- }
- $value.removeAttr('style').html(template);
- $sidebarCollapsedValue.text(labelCount);
-
- if (data.labels.length) {
- labelTitles = data.labels.map(function(label) {
- return label.title;
- });
-
- if (labelTitles.length > 5) {
- labelTitles = labelTitles.slice(0, 5);
- labelTitles.push('and ' + (data.labels.length - 5) + ' more');
+ axios.put(issueUpdateURL, data)
+ .then(({ data }) => {
+ var labelCount, template, labelTooltipTitle, labelTitles;
+ $loading.fadeOut();
+ $dropdown.trigger('loaded.gl.dropdown');
+ $selectbox.hide();
+ data.issueURLSplit = issueURLSplit;
+ labelCount = 0;
+ if (data.labels.length) {
+ template = labelHTMLTemplate(data);
+ labelCount = data.labels.length;
}
-
- labelTooltipTitle = labelTitles.join(', ');
- }
- else {
- labelTooltipTitle = '';
- $sidebarLabelTooltip.tooltip('destroy');
- }
-
- $sidebarLabelTooltip
- .attr('title', labelTooltipTitle)
- .tooltip('fixTitle');
-
- $('.has-tooltip', $value).tooltip({
- container: 'body'
- });
- });
+ else {
+ template = labelNoneHTMLTemplate;
+ }
+ $value.removeAttr('style').html(template);
+ $sidebarCollapsedValue.text(labelCount);
+
+ if (data.labels.length) {
+ labelTitles = data.labels.map(function(label) {
+ return label.title;
+ });
+
+ if (labelTitles.length > 5) {
+ labelTitles = labelTitles.slice(0, 5);
+ labelTitles.push('and ' + (data.labels.length - 5) + ' more');
+ }
+
+ labelTooltipTitle = labelTitles.join(', ');
+ }
+ else {
+ labelTooltipTitle = '';
+ $sidebarLabelTooltip.tooltip('destroy');
+ }
+
+ $sidebarLabelTooltip
+ .attr('title', labelTooltipTitle)
+ .tooltip('fixTitle');
+
+ $('.has-tooltip', $value).tooltip({
+ container: 'body'
+ });
+ })
+ .catch(() => flash(__('Error saving label update.')));
};
$dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
- return $.ajax({
- url: labelUrl
- }).done(function(data) {
- data = _.chain(data).groupBy(function(label) {
- return label.title;
- }).map(function(label) {
- var color;
- color = _.map(label, function(dup) {
- return dup.color;
- });
- return {
- id: label[0].id,
- title: label[0].title,
- color: color,
- duplicate: color.length > 1
- };
- }).value();
- if ($dropdown.hasClass('js-extra-options')) {
- var extraData = [];
- if (showNo) {
- extraData.unshift({
- id: 0,
- title: 'No Label'
- });
- }
- if (showAny) {
- extraData.unshift({
- isAny: true,
- title: 'Any Label'
+ axios.get(labelUrl)
+ .then((res) => {
+ let data = _.chain(res.data).groupBy(function(label) {
+ return label.title;
+ }).map(function(label) {
+ var color;
+ color = _.map(label, function(dup) {
+ return dup.color;
});
+ return {
+ id: label[0].id,
+ title: label[0].title,
+ color: color,
+ duplicate: color.length > 1
+ };
+ }).value();
+ if ($dropdown.hasClass('js-extra-options')) {
+ var extraData = [];
+ if (showNo) {
+ extraData.unshift({
+ id: 0,
+ title: 'No Label'
+ });
+ }
+ if (showAny) {
+ extraData.unshift({
+ isAny: true,
+ title: 'Any Label'
+ });
+ }
+ if (extraData.length) {
+ extraData.push('divider');
+ data = extraData.concat(data);
+ }
}
- if (extraData.length) {
- extraData.push('divider');
- data = extraData.concat(data);
+
+ callback(data);
+ if (showMenuAbove) {
+ $dropdown.data('glDropdown').positionMenuAbove();
}
- }
-
- callback(data);
- if (showMenuAbove) {
- $dropdown.data('glDropdown').positionMenuAbove();
- }
- });
+ })
+ .catch(() => flash(__('Error fetching labels.')));
},
renderRow: function(label, instance) {
var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue;
--
cgit v1.2.1
From 8750507b5db62e7821380d075d46d1a1c4eaca9d Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Tue, 30 Jan 2018 16:47:19 +0000
Subject: Convert merge_conflict_service.js to axios
---
.../merge_conflicts/merge_conflict_service.js | 14 +++-----------
.../merge_conflicts/merge_conflicts_bundle.js | 18 ++++++++----------
2 files changed, 11 insertions(+), 21 deletions(-)
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
index c012b77e0bf..c68b47c9348 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
@@ -1,4 +1,5 @@
/* eslint-disable no-param-reassign, comma-dangle */
+import axios from '../lib/utils/axios_utils';
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
@@ -10,20 +11,11 @@
}
fetchConflictsData() {
- return $.ajax({
- dataType: 'json',
- url: this.conflictsPath
- });
+ return axios.get(this.conflictsPath);
}
submitResolveConflicts(data) {
- return $.ajax({
- url: this.resolveConflictsPath,
- data: JSON.stringify(data),
- contentType: 'application/json',
- dataType: 'json',
- method: 'POST'
- });
+ return axios.post(this.resolveConflictsPath, data);
}
}
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 792b7523889..bc805047f73 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -38,24 +38,22 @@ $(() => {
showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent(); }
},
created() {
- mergeConflictsService
- .fetchConflictsData()
- .done((data) => {
+ mergeConflictsService.fetchConflictsData()
+ .then(({ data }) => {
if (data.type === 'error') {
mergeConflictsStore.setFailedRequest(data.message);
} else {
mergeConflictsStore.setConflictsData(data);
}
- })
- .error(() => {
- mergeConflictsStore.setFailedRequest();
- })
- .always(() => {
+
mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => {
syntaxHighlight($('.js-syntax-highlight'));
});
+ })
+ .catch(() => {
+ mergeConflictsStore.setFailedRequest();
});
},
methods: {
@@ -82,10 +80,10 @@ $(() => {
mergeConflictsService
.submitResolveConflicts(mergeConflictsStore.getCommitData())
- .done((data) => {
+ .then(({ data }) => {
window.location.href = data.redirect_to;
})
- .error(() => {
+ .catch(() => {
mergeConflictsStore.setSubmitState(false);
new Flash('Failed to save merge conflicts resolutions. Please try again!');
});
--
cgit v1.2.1
From a6dbb85e978cb05255fd78de44d6f7b364f6dabc Mon Sep 17 00:00:00 2001
From: Jacob Vosmaer
Date: Mon, 29 Jan 2018 17:46:23 +0100
Subject: Remove Rugged exception in cache rescue
---
app/models/repository.rb | 2 +-
lib/gitlab/git/tree.rb | 2 ++
spec/lib/gitlab/git/tree_spec.rb | 20 ++++++++------------
spec/models/repository_spec.rb | 8 +++-----
4 files changed, 14 insertions(+), 18 deletions(-)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 872d4468ac8..21a5c6c17cd 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -949,7 +949,7 @@ class Repository
end
instance_variable_set(ivar, value)
- rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
+ rescue Gitlab::Git::Repository::NoRepository
# Even if the above `#exists?` check passes these errors might still
# occur (for example because of a non-existing HEAD). We want to
# gracefully handle this and not cache anything
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index 5cf336af3c6..ba6058fd3c9 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -83,6 +83,8 @@ module Gitlab
commit_id: sha
)
end
+ rescue Rugged::ReferenceError
+ []
end
end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 86f7bcb8e38..001e406a930 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -80,22 +80,18 @@ describe Gitlab::Git::Tree, seed_helper: true do
end
describe '#where' do
- context 'with gitaly disabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
- end
-
- it 'calls #tree_entries_from_rugged' do
- expect(described_class).to receive(:tree_entries_from_rugged)
-
- described_class.where(repository, SeedRepo::Commit::ID, '/')
+ shared_examples '#where' do
+ it 'returns an empty array when called with an invalid ref' do
+ expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([])
end
end
- it 'gets the tree entries from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries)
+ context 'with gitaly' do
+ it_behaves_like '#where'
+ end
- described_class.where(repository, SeedRepo::Commit::ID, '/')
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '#where'
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index d4070b320ed..1102b1c9006 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -772,8 +772,7 @@ describe Repository do
user, 'LICENSE', 'Copyright!',
message: 'Add LICENSE', branch_name: 'master')
- allow(repository).to receive(:file_on_head)
- .and_raise(Rugged::ReferenceError)
+ allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.license_blob).to be_nil
end
@@ -885,7 +884,7 @@ describe Repository do
end
it 'returns nil for empty repository' do
- allow(repository).to receive(:file_on_head).and_raise(Rugged::ReferenceError)
+ allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.gitlab_ci_yml).to be_nil
end
end
@@ -1937,8 +1936,7 @@ describe Repository do
describe '#avatar' do
it 'returns nil if repo does not exist' do
- expect(repository).to receive(:file_on_head)
- .and_raise(Rugged::ReferenceError)
+ allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.avatar).to eq(nil)
end
--
cgit v1.2.1
From 804c6f93b72677a836ebe0aa8c6c7cdd0d07b8a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Tue, 30 Jan 2018 18:22:35 +0100
Subject: Ignore conflicts in db/schema.rb in Gitlab::EeCompatCheck
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The conflicts are simple enought to resolve that we don't need to bother
developers with those.
Signed-off-by: Rémy Coutable
---
lib/gitlab/ee_compat_check.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index d3b49b1ec75..0fb71976883 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -5,7 +5,7 @@ module Gitlab
DEFAULT_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
CHECK_DIR = Rails.root.join('ee_compat_check')
- IGNORED_FILES_REGEX = /(VERSION|CHANGELOG\.md:\d+)/.freeze
+ IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb}i.freeze
PLEASE_READ_THIS_BANNER = %Q{
============================================================
===================== PLEASE READ THIS =====================
--
cgit v1.2.1
From 58eb3c55c9d15bd604b926ffeae9401f0b70c53c Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Tue, 30 Jan 2018 17:23:27 +0000
Subject: Converted merge_request_tabs.js to axios
---
app/assets/javascripts/merge_request_tabs.js | 39 ++++++-----
spec/javascripts/merge_request_tabs_spec.js | 96 +++++++++++++++++++---------
2 files changed, 84 insertions(+), 51 deletions(-)
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index acfc62fe5cb..f69506a0471 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,6 +1,7 @@
/* eslint-disable no-new, class-methods-use-this */
import Cookies from 'js-cookie';
+import axios from './lib/utils/axios_utils';
import Flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
@@ -244,15 +245,19 @@ export default class MergeRequestTabs {
if (this.commitsLoaded) {
return;
}
- this.ajaxGet({
- url: `${source}.json`,
- success: (data) => {
+
+ this.toggleLoading(true)
+
+ axios.get(`${source}.json`)
+ .then(({ data }) => {
document.querySelector('div#commits').innerHTML = data.html;
localTimeAgo($('.js-timeago', 'div#commits'));
this.commitsLoaded = true;
this.scrollToElement('#commits');
- },
- });
+
+ this.toggleLoading(false);
+ })
+ .catch(() => new Flash('An error occurred while fetching this tab.', 'alert'));
}
mountPipelinesView() {
@@ -283,9 +288,10 @@ export default class MergeRequestTabs {
// some pages like MergeRequestsController#new has query parameters on that anchor
const urlPathname = parseUrlPathname(source);
- this.ajaxGet({
- url: `${urlPathname}.json${location.search}`,
- success: (data) => {
+ this.toggleLoading(true);
+
+ axios.get(`${urlPathname}.json${location.search}`)
+ .then(({ data }) => {
const $container = $('#diffs');
$container.html(data.html);
@@ -335,8 +341,10 @@ export default class MergeRequestTabs {
// (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target');
}
- },
- });
+
+ this.toggleLoading(false);
+ })
+ .catch(() => Flash('An error occurred while fetching this tab.', 'alert'));
}
// Show or hide the loading spinner
@@ -346,17 +354,6 @@ export default class MergeRequestTabs {
$('.mr-loading-status .loading').toggle(status);
}
- ajaxGet(options) {
- const defaults = {
- beforeSend: () => this.toggleLoading(true),
- error: () => new Flash('An error occurred while fetching this tab.', 'alert'),
- complete: () => this.toggleLoading(false),
- dataType: 'json',
- type: 'GET',
- };
- $.ajax($.extend({}, defaults, options));
- }
-
diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index a6be474805b..116c4e8f0e4 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable no-var, comma-dangle, object-shorthand */
-
+import MockAdaptor from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
@@ -46,7 +47,7 @@ import 'vendor/jquery.scrollTo';
describe('activateTab', function () {
beforeEach(function () {
- spyOn($, 'ajax').and.callFake(function () {});
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.subject = this.class.activateTab;
});
@@ -148,7 +149,7 @@ import 'vendor/jquery.scrollTo';
describe('setCurrentAction', function () {
beforeEach(function () {
- spyOn($, 'ajax').and.callFake(function () {});
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
this.subject = this.class.setCurrentAction;
});
@@ -214,13 +215,21 @@ import 'vendor/jquery.scrollTo';
});
describe('tabShown', () => {
+ let mock;
+
beforeEach(function () {
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success({ html: '' });
+ mock = new MockAdaptor(axios);
+ mock.onGet(/(.*)\/diffs\.json/).reply(200, {
+ data: { html: '' },
});
+
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
});
+ afterEach(() => {
+ mock.restore();
+ });
+
describe('with "Side-by-side"/parallel diff view', () => {
beforeEach(function () {
this.class.diffViewType = () => 'parallel';
@@ -292,16 +301,20 @@ import 'vendor/jquery.scrollTo';
it('triggers Ajax request to JSON endpoint', function (done) {
const url = '/foo/bar/merge_requests/1/diffs';
- spyOn(this.class, 'ajaxGet').and.callFake((options) => {
- expect(options.url).toEqual(`${url}.json`);
+
+ spyOn(axios, 'get').and.callFake((reqUrl) => {
+ expect(reqUrl).toBe(`${url}.json`);
+
done();
+
+ return Promise.resolve({ data: {} });
});
this.class.loadDiff(url);
});
it('triggers scroll event when diff already loaded', function (done) {
- spyOn(this.class, 'ajaxGet').and.callFake(() => done.fail());
+ spyOn(axios, 'get').and.callFake(done.fail);
spyOn(document, 'dispatchEvent');
this.class.diffsLoaded = true;
@@ -316,6 +329,7 @@ import 'vendor/jquery.scrollTo';
describe('with inline diff', () => {
let noteId;
let noteLineNumId;
+ let mock;
beforeEach(() => {
const diffsResponse = getJSONFixture(inlineChangesTabJsonFixture);
@@ -330,29 +344,40 @@ import 'vendor/jquery.scrollTo';
.attr('href')
.replace('#', '');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success(diffsResponse);
- });
+ mock = new MockAdaptor(axios);
+ mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
+ });
+
+ afterEach(() => {
+ mock.restore();
});
describe('with note fragment hash', () => {
- it('should expand and scroll to linked fragment hash #note_xxx', function () {
+ it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(noteId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
- target: jasmine.any(Object),
- lineType: 'old',
- forceShow: true,
+ setTimeout(() => {
+ expect(noteId.length).toBeGreaterThan(0);
+ expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
+ target: jasmine.any(Object),
+ lineType: 'old',
+ forceShow: true,
+ });
+
+ done();
});
});
- it('should gracefully ignore non-existant fragment hash', function () {
+ it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+ setTimeout(() => {
+ expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+
+ done();
+ });
});
});
@@ -370,6 +395,7 @@ import 'vendor/jquery.scrollTo';
describe('with parallel diff', () => {
let noteId;
let noteLineNumId;
+ let mock;
beforeEach(() => {
const diffsResponse = getJSONFixture(parallelChangesTabJsonFixture);
@@ -384,30 +410,40 @@ import 'vendor/jquery.scrollTo';
.attr('href')
.replace('#', '');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success(diffsResponse);
- });
+ mock = new MockAdaptor(axios);
+ mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
+ });
+
+ afterEach(() => {
+ mock.restore();
});
describe('with note fragment hash', () => {
- it('should expand and scroll to linked fragment hash #note_xxx', function () {
+ it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(noteId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
- target: jasmine.any(Object),
- lineType: 'new',
- forceShow: true,
+ setTimeout(() => {
+ expect(noteId.length).toBeGreaterThan(0);
+ expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
+ target: jasmine.any(Object),
+ lineType: 'new',
+ forceShow: true,
+ });
+
+ done();
});
});
- it('should gracefully ignore non-existant fragment hash', function () {
+ it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+ setTimeout(() => {
+ expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+ done();
+ });
});
});
--
cgit v1.2.1
From e94e42ba1d4d4972e87ca6e39e987628ab97f1fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Tue, 30 Jan 2018 18:26:32 +0100
Subject: Update Nokogiri to 1.8.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
changelogs/unreleased/42591-update-nokogiri.yml | 5 +++++
3 files changed, 8 insertions(+), 3 deletions(-)
create mode 100644 changelogs/unreleased/42591-update-nokogiri.yml
diff --git a/Gemfile b/Gemfile
index 05f72b6482f..8f2a585a624 100644
--- a/Gemfile
+++ b/Gemfile
@@ -132,7 +132,7 @@ gem 'asciidoctor-plantuml', '0.0.7'
gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
-gem 'nokogiri', '~> 1.8.1'
+gem 'nokogiri', '~> 1.8.2'
# Diffs
gem 'diffy', '~> 3.1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1a3c8f42469..ca032a4b2cc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -513,7 +513,7 @@ GEM
net-ldap (0.16.0)
net-ssh (4.1.0)
netrc (0.11.0)
- nokogiri (1.8.1)
+ nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
numerizer (0.1.1)
oauth (0.5.1)
@@ -1100,7 +1100,7 @@ DEPENDENCIES
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 4.1.0)
- nokogiri (~> 1.8.1)
+ nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
oj (~> 2.17.4)
diff --git a/changelogs/unreleased/42591-update-nokogiri.yml b/changelogs/unreleased/42591-update-nokogiri.yml
new file mode 100644
index 00000000000..5f9587d2d92
--- /dev/null
+++ b/changelogs/unreleased/42591-update-nokogiri.yml
@@ -0,0 +1,5 @@
+---
+title: Update nokogiri to 1.8.2
+merge_request: 16807
+author:
+type: security
--
cgit v1.2.1
From c7c3259eec6e20aa69b8b89af328a7d6a55379a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Tue, 30 Jan 2018 18:32:32 +0100
Subject: Remove brakeman from .codeclimate.yml since it's now covered by the
sast CI job
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
.codeclimate.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.codeclimate.yml b/.codeclimate.yml
index dc8ac60fb44..ecac24b68d7 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,7 +1,5 @@
---
engines:
- brakeman:
- enabled: true
bundler-audit:
enabled: true
duplication:
--
cgit v1.2.1
From 11191f938c3b865dbb3e203ecdb018fd91bd0c91 Mon Sep 17 00:00:00 2001
From: Ahmad Sherif
Date: Mon, 29 Jan 2018 18:22:33 +0100
Subject: Move Repository#can_be_merged? to Gitlab::Git::Repository
Fixes #42544
---
app/models/repository.rb | 20 +-------------------
lib/gitlab/git/repository.rb | 18 ++++++++++++++++++
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 5b06dc5a39b..49a094ab2da 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -800,16 +800,6 @@ class Repository
with_cache_hooks { raw.multi_action(user, **options) }
end
- def can_be_merged?(source_sha, target_branch)
- raw_repository.gitaly_migrate(:can_be_merged) do |is_enabled|
- if is_enabled
- gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
- else
- rugged_can_be_merged?(source_sha, target_branch)
- end
- end
- end
-
def merge(user, source_sha, merge_request, message)
with_cache_hooks do
raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
@@ -876,7 +866,7 @@ class Repository
@root_ref_sha ||= commit(root_ref).sha
end
- delegate :merged_branch_names, to: :raw_repository
+ delegate :merged_branch_names, :can_be_merged?, to: :raw_repository
def merge_base(first_commit_id, second_commit_id)
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
@@ -1095,12 +1085,4 @@ class Repository
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
end
-
- def gitaly_can_be_merged?(their_commit, our_commit)
- !raw_repository.gitaly_conflicts_client(our_commit, their_commit).conflicts?
- end
-
- def rugged_can_be_merged?(their_commit, our_commit)
- !rugged.merge_commits(our_commit, their_commit).conflicts?
- end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 3a7930154e5..ff2a5df5676 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1392,6 +1392,16 @@ module Gitlab
run_git(args).first.scrub.split(/^--$/)
end
+ def can_be_merged?(source_sha, target_branch)
+ gitaly_migrate(:can_be_merged) do |is_enabled|
+ if is_enabled
+ gitaly_can_be_merged?(source_sha, find_branch(target_branch, true).target)
+ else
+ rugged_can_be_merged?(source_sha, target_branch)
+ end
+ end
+ end
+
def search_files_by_name(query, ref)
safe_query = Regexp.escape(query.sub(/^\/*/, ""))
@@ -2234,6 +2244,14 @@ module Gitlab
run_git(['fetch', remote_name], env: env).last.zero?
end
+ def gitaly_can_be_merged?(their_commit, our_commit)
+ !gitaly_conflicts_client(our_commit, their_commit).conflicts?
+ end
+
+ def rugged_can_be_merged?(their_commit, our_commit)
+ !rugged.merge_commits(our_commit, their_commit).conflicts?
+ end
+
def gitlab_projects_error
raise CommandError, @gitlab_projects.output
end
--
cgit v1.2.1
From 564306e028f2575262884937cba0c38eca586d7b Mon Sep 17 00:00:00 2001
From: Nick Thomas
Date: Tue, 30 Jan 2018 18:55:51 +0000
Subject: Don't assume postgresql in two initializers
---
config/initializers/ar5_pg_10_support.rb | 88 ++++++++++++++++----------------
config/initializers/peek.rb | 4 +-
2 files changed, 48 insertions(+), 44 deletions(-)
diff --git a/config/initializers/ar5_pg_10_support.rb b/config/initializers/ar5_pg_10_support.rb
index 6fae770015c..a529c74a8ce 100644
--- a/config/initializers/ar5_pg_10_support.rb
+++ b/config/initializers/ar5_pg_10_support.rb
@@ -1,57 +1,59 @@
raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5
-require 'active_record/connection_adapters/postgresql_adapter'
-require 'active_record/connection_adapters/postgresql/schema_statements'
-
-#
-# Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330
-#
-# Updates sequence logic to support PostgreSQL 10.
-#
-# rubocop:disable all
-module ActiveRecord
- module ConnectionAdapters
-
- # We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
- # to work. In ActiveRecord 4, it is protected.
- # https://github.com/mbleigh/seed-fu/issues/123
- class PostgreSQLAdapter
- public :postgresql_version
- end
+if Gitlab::Database.postgresql?
+ require 'active_record/connection_adapters/postgresql_adapter'
+ require 'active_record/connection_adapters/postgresql/schema_statements'
+
+ #
+ # Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330
+ #
+ # Updates sequence logic to support PostgreSQL 10.
+ #
+ # rubocop:disable all
+ module ActiveRecord
+ module ConnectionAdapters
+
+ # We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
+ # to work. In ActiveRecord 4, it is protected.
+ # https://github.com/mbleigh/seed-fu/issues/123
+ class PostgreSQLAdapter
+ public :postgresql_version
+ end
- module PostgreSQL
- module SchemaStatements
- # Resets the sequence of a table's primary key to the maximum value.
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
- unless pk and sequence
- default_pk, default_sequence = pk_and_sequence_for(table)
+ module PostgreSQL
+ module SchemaStatements
+ # Resets the sequence of a table's primary key to the maximum value.
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
+ unless pk and sequence
+ default_pk, default_sequence = pk_and_sequence_for(table)
- pk ||= default_pk
- sequence ||= default_sequence
- end
+ pk ||= default_pk
+ sequence ||= default_sequence
+ end
- if @logger && pk && !sequence
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
- end
+ if @logger && pk && !sequence
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ end
- if pk && sequence
- quoted_sequence = quote_table_name(sequence)
- max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}")
- if max_pk.nil?
- if postgresql_version >= 100000
- minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass")
- else
- minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
+ if pk && sequence
+ quoted_sequence = quote_table_name(sequence)
+ max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}")
+ if max_pk.nil?
+ if postgresql_version >= 100000
+ minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass")
+ else
+ minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
+ end
end
- end
- select_value <<-end_sql, 'SCHEMA'
- SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
- end_sql
+ select_value <<-end_sql, 'SCHEMA'
+ SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
+ end_sql
+ end
end
end
end
end
end
+ # rubocop:enable all
end
-# rubocop:enable all
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index e74b95f1646..11759801112 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -7,10 +7,12 @@ if Gitlab::Database.mysql?
require 'peek-mysql2'
PEEK_DB_CLIENT = ::Mysql2::Client
PEEK_DB_VIEW = Peek::Views::Mysql2
-else
+elsif Gitlab::Database.postgresql?
require 'peek-pg'
PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG
+else
+ raise "Unsupported database adapter for peek!"
end
Peek.into PEEK_DB_VIEW
--
cgit v1.2.1
From 470e83ee9f4e82be37fa37345cb9358134c5552e Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin
Date: Wed, 31 Jan 2018 01:00:09 +0800
Subject: Make those files as close as EE to reduce conflicts
---
spec/requests/api/jobs_spec.rb | 71 +++++++++++++++++++++----------------
spec/requests/api/v3/builds_spec.rb | 54 ++++++++++++++++------------
2 files changed, 73 insertions(+), 52 deletions(-)
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 4dd8deb6404..f8d0b63afec 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -300,44 +300,53 @@ describe API::Jobs do
end
describe 'GET /projects/:id/jobs/:job_id/artifacts' do
- before do
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ shared_examples 'downloads artifact' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'returns specific job artifacts' do
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.headers).to include(download_headers)
+ expect(response.body).to match_file(job.artifacts_file.file.file)
+ end
end
- context 'job with artifacts' do
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+ context 'normal authentication' do
+ context 'job with artifacts' do
+ context 'when artifacts are stored locally' do
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
- context 'authorized user' do
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
- end
+ before do
+ get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ end
- it 'returns specific job artifacts' do
- expect(response).to have_gitlab_http_status(200)
- expect(response.headers).to include(download_headers)
- expect(response.body).to match_file(job.artifacts_file.file.file)
+ context 'authorized user' do
+ it_behaves_like 'downloads artifact'
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return specific job artifacts' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
- end
- context 'when anonymous user is accessing private artifacts' do
- let(:api_user) { nil }
+ it 'does not return job artifacts if not uploaded' do
+ get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
- it 'hides artifacts and rejects request' do
- expect(project).to be_private
expect(response).to have_gitlab_http_status(404)
end
end
end
-
- it 'does not return job artifacts if not uploaded' do
- expect(response).to have_gitlab_http_status(404)
- end
end
describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
let(:api_user) { reporter }
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
before do
job.success
@@ -396,14 +405,16 @@ describe API::Jobs do
context 'find proper job' do
shared_examples 'a valid file' do
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' =>
- "attachment; filename=#{job.artifacts_file.filename}" }
- end
+ context 'when artifacts are stored locally' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' =>
+ "attachment; filename=#{job.artifacts_file.filename}" }
+ end
- it { expect(response).to have_gitlab_http_status(200) }
- it { expect(response.headers).to include(download_headers) }
+ it { expect(response).to have_gitlab_http_status(200) }
+ it { expect(response.headers).to include(download_headers) }
+ end
end
context 'with regular branch' do
diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb
index af9e36a3b29..3f92288fef0 100644
--- a/spec/requests/api/v3/builds_spec.rb
+++ b/spec/requests/api/v3/builds_spec.rb
@@ -4,16 +4,18 @@ describe API::V3::Builds do
set(:user) { create(:user) }
let(:api_user) { user }
set(:project) { create(:project, :repository, creator: user, public_builds: false) }
- set(:developer) { create(:project_member, :developer, user: user, project: project) }
- set(:reporter) { create(:project_member, :reporter, project: project) }
- set(:guest) { create(:project_member, :guest, project: project) }
- set(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
- let!(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:developer) { create(:project_member, :developer, user: user, project: project) }
+ let(:reporter) { create(:project_member, :reporter, project: project) }
+ let(:guest) { create(:project_member, :guest, project: project) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
before do |example|
+ build
+
create(:ci_build, :skipped, pipeline: pipeline)
unless example.metadata[:skip_before_request]
@@ -110,6 +112,10 @@ describe API::V3::Builds do
end
describe 'GET /projects/:id/repository/commits/:sha/builds' do
+ before do
+ build
+ end
+
context 'when commit does not exist in repository' do
before do
get v3_api("/projects/#{project.id}/repository/commits/1a271fd1/builds", api_user)
@@ -214,18 +220,20 @@ describe API::V3::Builds do
end
context 'job with artifacts' do
- let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
+ context 'when artifacts are stored locally' do
+ let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
- context 'authorized user' do
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
- end
+ context 'authorized user' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
+ end
- it 'returns specific job artifacts' do
- expect(response).to have_gitlab_http_status(200)
- expect(response.headers).to include(download_headers)
- expect(response.body).to match_file(build.artifacts_file.file.file)
+ it 'returns specific job artifacts' do
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.headers).to include(download_headers)
+ expect(response.body).to match_file(build.artifacts_file.file.file)
+ end
end
end
@@ -303,14 +311,16 @@ describe API::V3::Builds do
context 'find proper job' do
shared_examples 'a valid file' do
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' =>
- "attachment; filename=#{build.artifacts_file.filename}" }
- end
+ context 'when artifacts are stored locally' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' =>
+ "attachment; filename=#{build.artifacts_file.filename}" }
+ end
- it { expect(response).to have_gitlab_http_status(200) }
- it { expect(response.headers).to include(download_headers) }
+ it { expect(response).to have_gitlab_http_status(200) }
+ it { expect(response.headers).to include(download_headers) }
+ end
end
context 'with regular branch' do
--
cgit v1.2.1
From d700659b04b6318a35c7637f9af04c52df63a87b Mon Sep 17 00:00:00 2001
From: Filipa Lacerda
Date: Tue, 30 Jan 2018 20:34:11 +0000
Subject: Move mr widget related links into a vue file
---
.../components/mr_widget_related_links.js | 37 ------
.../components/mr_widget_related_links.vue | 42 +++++++
.../vue_merge_request_widget/dependencies.js | 2 +-
.../vue_merge_request_widget/mr_widget_options.js | 6 +-
.../components/mr_widget_related_links_spec.js | 131 ++++++++-------------
5 files changed, 95 insertions(+), 123 deletions(-)
delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js
create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js
deleted file mode 100644
index 563267ad044..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js
+++ /dev/null
@@ -1,37 +0,0 @@
-export default {
- name: 'MRWidgetRelatedLinks',
- props: {
- relatedLinks: { type: Object, required: true },
- state: { type: String, required: false },
- },
- computed: {
- hasLinks() {
- const { closing, mentioned, assignToMe } = this.relatedLinks;
- return closing || mentioned || assignToMe;
- },
- closesText() {
- if (this.state === 'merged') {
- return 'Closed';
- }
- if (this.state === 'closed') {
- return 'Did not close';
- }
- return 'Closes';
- },
- },
- template: `
-
-
- {{closesText}}
-
-
- Mentions
-
-
-
-
-
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
new file mode 100644
index 00000000000..080ec1c579c
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
@@ -0,0 +1,42 @@
+
+
+
+
+ {{ closesText }}
+
+
+ Mentions
+
+
+
+
+
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 2917090e073..515eb32f5d6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -15,7 +15,7 @@ export { default as WidgetHeader } from './components/mr_widget_header';
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 WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
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';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 98d33f9efaa..6d3573de76c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -63,7 +63,8 @@ export default {
return this.mr.hasCI;
},
shouldRenderRelatedLinks() {
- return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
+ const { closing, mentioned, assignToMe } = this.mr.relatedLinks;
+ return (closing || mentioned || assignToMe) && !this.mr.isNothingToMergeState;
},
shouldRenderDeployments() {
return this.mr.deployments.length;
@@ -257,7 +258,8 @@ export default {
+ :related-links="mr.relatedLinks"
+ />
//
- // Only load once
+
+import { __ } from './locale';
+import axios from './lib/utils/axios_utils';
+import flash from './flash';
+
+// Only load once
let katexLoaded = false;
// Loop over all math elements and render math
@@ -33,19 +38,26 @@ export default function renderMath($els) {
if (katexLoaded) {
renderWithKaTeX($els);
} else {
- $.get(gon.katex_css_url, () => {
- const css = $(' ', {
- rel: 'stylesheet',
- type: 'text/css',
- href: gon.katex_css_url,
- });
- css.appendTo('head');
-
- // Load KaTeX js
- $.getScript(gon.katex_js_url, () => {
+ axios.get(gon.katex_css_url)
+ .then(() => {
+ const css = $(' ', {
+ rel: 'stylesheet',
+ type: 'text/css',
+ href: gon.katex_css_url,
+ });
+ css.appendTo('head');
+ })
+ .then(() => axios.get(gon.katex_js_url, {
+ responseType: 'text',
+ }))
+ .then(({ data }) => {
+ // Add katex js to our document
+ $.globalEval(data);
+ })
+ .then(() => {
katexLoaded = true;
renderWithKaTeX($els); // Run KaTeX
- });
- });
+ })
+ .catch(() => flash(__('An error occurred while rendering KaTeX')));
}
}
--
cgit v1.2.1
From 1ebb781f6a5e25a5e71c77be67f4f53e9c4e72c3 Mon Sep 17 00:00:00 2001
From: Ahmad Sherif
Date: Thu, 25 Jan 2018 20:59:33 +0100
Subject: Migrate Git::Repository#delete_refs to Gitaly
Closes gitaly#967
---
GITALY_SERVER_VERSION | 2 +-
Gemfile | 2 +-
Gemfile.lock | 4 +--
lib/gitlab/git/repository.rb | 34 ++++++++++++------
lib/gitlab/gitaly_client/ref_service.rb | 9 +++--
spec/lib/gitlab/git/repository_spec.rb | 44 ++++++++++++-----------
spec/lib/gitlab/gitaly_client/ref_service_spec.rb | 2 +-
7 files changed, 59 insertions(+), 38 deletions(-)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index c52842c467c..62df9f538d8 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.75.0
+0.76.0
diff --git a/Gemfile b/Gemfile
index d9110f9b5b2..7bffae06b51 100644
--- a/Gemfile
+++ b/Gemfile
@@ -406,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.82.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index a8531f8e436..4558b43af48 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.82.0)
+ gitaly-proto (0.83.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.82.0)
+ gitaly-proto (~> 0.83.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 8614c1bd417..7127f7858ee 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -888,16 +888,12 @@ module Gitlab
end
def delete_refs(*ref_names)
- instructions = ref_names.map do |ref|
- "delete #{ref}\x00\x00"
- end
-
- message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
- stdin.write(instructions.join)
- end
-
- unless status.zero?
- raise GitError.new("Could not delete refs #{ref_names}: #{message}")
+ gitaly_migrate(:delete_refs) do |is_enabled|
+ if is_enabled
+ gitaly_delete_refs(*ref_names)
+ else
+ git_delete_refs(*ref_names)
+ end
end
end
@@ -2204,6 +2200,24 @@ module Gitlab
remote_update(remote_name, url: url)
end
+ def git_delete_refs(*ref_names)
+ instructions = ref_names.map do |ref|
+ "delete #{ref}\x00\x00"
+ end
+
+ message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
+ stdin.write(instructions.join)
+ end
+
+ unless status.zero?
+ raise GitError.new("Could not delete refs #{ref_names}: #{message}")
+ end
+ end
+
+ def gitaly_delete_refs(*ref_names)
+ gitaly_ref_client.delete_refs(refs: ref_names)
+ end
+
def rugged_remove_remote(remote_name)
# When a remote is deleted all its remote refs are deleted too, but in
# the case of mirrors we map its refs (that would usualy go under
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index f8e2a27f3fe..8b9a224b700 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -133,13 +133,16 @@ module Gitlab
GitalyClient.call(@repository.storage, :ref_service, :delete_branch, request)
end
- def delete_refs(except_with_prefixes:)
+ def delete_refs(refs: [], except_with_prefixes: [])
request = Gitaly::DeleteRefsRequest.new(
repository: @gitaly_repo,
- except_with_prefix: except_with_prefixes
+ refs: refs.map { |r| encode_binary(r) },
+ except_with_prefix: except_with_prefixes.map { |r| encode_binary(r) }
)
- GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request)
+ response = GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request)
+
+ raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
end
private
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index ac7c0270916..bf01e6ef8e8 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -562,35 +562,39 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#delete_refs' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ shared_examples 'deleting refs' do
+ let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- it 'deletes the ref' do
- @repo.delete_refs('refs/heads/feature')
+ after do
+ ensure_seeds
+ end
- expect(@repo.rugged.references['refs/heads/feature']).to be_nil
- end
+ it 'deletes the ref' do
+ repo.delete_refs('refs/heads/feature')
- it 'deletes all refs' do
- refs = %w[refs/heads/wip refs/tags/v1.1.0]
- @repo.delete_refs(*refs)
+ expect(repo.rugged.references['refs/heads/feature']).to be_nil
+ end
- refs.each do |ref|
- expect(@repo.rugged.references[ref]).to be_nil
+ it 'deletes all refs' do
+ refs = %w[refs/heads/wip refs/tags/v1.1.0]
+ repo.delete_refs(*refs)
+
+ refs.each do |ref|
+ expect(repo.rugged.references[ref]).to be_nil
+ end
end
- end
- it 'raises an error if it failed' do
- expect(@repo).to receive(:popen).and_return(['Error', 1])
+ it 'raises an error if it failed' do
+ expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
+ end
+ end
- expect do
- @repo.delete_refs('refs/heads/fix')
- end.to raise_error(Gitlab::Git::Repository::GitError)
+ context 'when Gitaly delete_refs feature is enabled' do
+ it_behaves_like 'deleting refs'
end
- after(:all) do
- ensure_seeds
+ context 'when Gitaly delete_refs feature is disabled', :disable_gitaly do
+ it_behaves_like 'deleting refs'
end
end
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index 951e146a30a..257e4c50f2d 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -112,7 +112,7 @@ describe Gitlab::GitalyClient::RefService do
expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:delete_refs)
.with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash))
- .and_return(double('delete_refs_response'))
+ .and_return(double('delete_refs_response', git_error: ""))
client.delete_refs(except_with_prefixes: prefixes)
end
--
cgit v1.2.1
From 5aeb5ff51e8d25791c400301a627932e2f4091e8 Mon Sep 17 00:00:00 2001
From: Ahmad Sherif
Date: Mon, 29 Jan 2018 16:11:16 +0100
Subject: Add support for PreReceiveError in UserMergeBranch RPC
Closes gitaly#979
---
GITALY_SERVER_VERSION | 2 +-
Gemfile | 2 +-
Gemfile.lock | 4 ++--
lib/gitlab/gitaly_client/operation_service.rb | 8 +++++++-
4 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index c52842c467c..62df9f538d8 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.75.0
+0.76.0
diff --git a/Gemfile b/Gemfile
index d9110f9b5b2..7bffae06b51 100644
--- a/Gemfile
+++ b/Gemfile
@@ -406,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.82.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index a8531f8e436..4558b43af48 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.82.0)
+ gitaly-proto (0.83.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.82.0)
+ gitaly-proto (~> 0.83.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index c2b4155e6a5..cd2734b5a07 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -103,7 +103,13 @@ module Gitlab
request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true))
- branch_update = response_enum.next.branch_update
+ second_response = response_enum.next
+
+ if second_response.pre_receive_error.present?
+ raise Gitlab::Git::HooksService::PreReceiveError, second_response.pre_receive_error
+ end
+
+ branch_update = second_response.branch_update
return if branch_update.nil?
raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present?
--
cgit v1.2.1
From 30e6cfa05a361cbdc12ddb7c12061f4c7221c61b Mon Sep 17 00:00:00 2001
From: Stan Hu
Date: Tue, 30 Jan 2018 15:45:23 -0800
Subject: Fix not all events being shown in group dashboard
The group activity feed was limited to the first 20 projects found
in the group, which caused activity from some projects to be omitted.
A limit of 20 is applied to the query for events, so the
extra pagination does little in the way of performance.
Closes #42560
---
app/controllers/groups_controller.rb | 1 -
changelogs/unreleased/sh-fix-events-collection.yml | 5 +++++
spec/controllers/groups_controller_spec.rb | 24 ++++++++++++++++++++++
3 files changed, 29 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/sh-fix-events-collection.yml
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index eb53a522f90..75270a0889b 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -150,7 +150,6 @@ class GroupsController < Groups::ApplicationController
@projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user)
.execute
.includes(:namespace)
- .page(params[:page])
@events = EventCollection
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
diff --git a/changelogs/unreleased/sh-fix-events-collection.yml b/changelogs/unreleased/sh-fix-events-collection.yml
new file mode 100644
index 00000000000..50af39d9caf
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-events-collection.yml
@@ -0,0 +1,5 @@
+---
+title: Fix not all events being shown in group dashboard
+merge_request:
+author:
+type: fixed
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index a9cfd964dd5..492fed42d31 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -85,6 +85,30 @@ describe GroupsController do
end
end
+ describe 'GET #activity' do
+ render_views
+
+ before do
+ sign_in(user)
+ project
+ end
+
+ context 'as json' do
+ it 'includes all projects in event feed' do
+ 3.times do
+ project = create(:project, group: group)
+ create(:event, project: project)
+ end
+
+ get :activity, id: group.to_param, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['count']).to eq(3)
+ expect(assigns(:projects).limit_value).to be_nil
+ end
+ end
+ end
+
describe 'POST #create' do
context 'when creating subgroups', :nested_groups do
[true, false].each do |can_create_group_status|
--
cgit v1.2.1
From 46a6edc7314ce8acab5d8ce04799bd3557bc26bc Mon Sep 17 00:00:00 2001
From: Stan Hu
Date: Fri, 26 Jan 2018 22:16:56 -0800
Subject: Remove N+1 queries with
/projects/:project_id/{access_requests,members} API endpoints
We can simplify the code quite a bit and improve performance by using
grape-entity merge fields:
https://github.com/ruby-grape/grape-entity/tree/v0.6.0#merge-fields
Relates to #42030
---
.../unreleased/sh-fix-project-members-api-perf.yml | 6 ++++++
lib/api/access_requests.rb | 6 +++---
lib/api/entities.rb | 21 +++++++--------------
lib/api/members.rb | 13 +++++++------
lib/api/v3/members.rb | 15 ++++++++-------
spec/requests/api/members_spec.rb | 15 +++++++++++++++
6 files changed, 46 insertions(+), 30 deletions(-)
create mode 100644 changelogs/unreleased/sh-fix-project-members-api-perf.yml
diff --git a/changelogs/unreleased/sh-fix-project-members-api-perf.yml b/changelogs/unreleased/sh-fix-project-members-api-perf.yml
new file mode 100644
index 00000000000..c3fff933547
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-project-members-api-perf.yml
@@ -0,0 +1,6 @@
+---
+title: Remove N+1 queries with /projects/:project_id/{access_requests,members} API
+ endpoints
+merge_request:
+author:
+type: performance
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index 374b611f55e..60ae5e6b9a2 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -24,7 +24,7 @@ module API
access_requesters = AccessRequestsFinder.new(source).execute!(current_user)
access_requesters = paginate(access_requesters.includes(:user))
- present access_requesters.map(&:user), with: Entities::AccessRequester, source: source
+ present access_requesters, with: Entities::AccessRequester
end
desc "Requests access for the authenticated user to a #{source_type}." do
@@ -36,7 +36,7 @@ module API
access_requester = source.request_access(current_user)
if access_requester.persisted?
- present access_requester.user, with: Entities::AccessRequester, access_requester: access_requester
+ present access_requester, with: Entities::AccessRequester
else
render_validation_error!(access_requester)
end
@@ -56,7 +56,7 @@ module API
member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute
status :created
- present member.user, with: Entities::Member, member: member
+ present member, with: Entities::Member
end
desc 'Denies an access request for the given user.' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index cb222697f32..e13463ec66b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -205,22 +205,15 @@ module API
expose :build_artifacts_size, as: :job_artifacts_size
end
- class Member < UserBasic
- expose :access_level do |user, options|
- member = options[:member] || options[:source].members.find_by(user_id: user.id)
- member.access_level
- end
- expose :expires_at do |user, options|
- member = options[:member] || options[:source].members.find_by(user_id: user.id)
- member.expires_at
- end
+ class Member < Grape::Entity
+ expose :user, merge: true, using: UserBasic
+ expose :access_level
+ expose :expires_at
end
- class AccessRequester < UserBasic
- expose :requested_at do |user, options|
- access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id)
- access_requester.requested_at
- end
+ class AccessRequester < Grape::Entity
+ expose :user, merge: true, using: UserBasic
+ expose :requested_at
end
class Group < Grape::Entity
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 130c6d6da71..bc1de37284a 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -21,10 +21,11 @@ module API
get ":id/members" do
source = find_source(source_type, params[:id])
- users = source.users
- users = users.merge(User.search(params[:query])) if params[:query].present?
+ members = source.members.where.not(user_id: nil).includes(:user)
+ members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present?
+ members = paginate(members)
- present paginate(users), with: Entities::Member, source: source
+ present members, with: Entities::Member
end
desc 'Gets a member of a group or project.' do
@@ -39,7 +40,7 @@ module API
members = source.members
member = members.find_by!(user_id: params[:user_id])
- present member.user, with: Entities::Member, member: member
+ present member, with: Entities::Member
end
desc 'Adds a member to a group or project.' do
@@ -62,7 +63,7 @@ module API
if !member
not_allowed! # This currently can only be reached in EE
elsif member.persisted? && member.valid?
- present member.user, with: Entities::Member, member: member
+ present member, with: Entities::Member
else
render_validation_error!(member)
end
@@ -83,7 +84,7 @@ module API
member = source.members.find_by!(user_id: params.delete(:user_id))
if member.update_attributes(declared_params(include_missing: false))
- present member.user, with: Entities::Member, member: member
+ present member, with: Entities::Member
else
render_validation_error!(member)
end
diff --git a/lib/api/v3/members.rb b/lib/api/v3/members.rb
index 46145cac7a5..d7bde8ceb89 100644
--- a/lib/api/v3/members.rb
+++ b/lib/api/v3/members.rb
@@ -22,10 +22,11 @@ module API
get ":id/members" do
source = find_source(source_type, params[:id])
- users = source.users
- users = users.merge(User.search(params[:query])) if params[:query].present?
+ members = source.members.where.not(user_id: nil).includes(:user)
+ members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present?
+ members = paginate(members)
- present paginate(users), with: ::API::Entities::Member, source: source
+ present members, with: ::API::Entities::Member
end
desc 'Gets a member of a group or project.' do
@@ -40,7 +41,7 @@ module API
members = source.members
member = members.find_by!(user_id: params[:user_id])
- present member.user, with: ::API::Entities::Member, member: member
+ present member, with: ::API::Entities::Member
end
desc 'Adds a member to a group or project.' do
@@ -69,7 +70,7 @@ module API
end
if member.persisted? && member.valid?
- present member.user, with: ::API::Entities::Member, member: member
+ present member, with: ::API::Entities::Member
else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
@@ -93,7 +94,7 @@ module API
member = source.members.find_by!(user_id: params.delete(:user_id))
if member.update_attributes(declared_params(include_missing: false))
- present member.user, with: ::API::Entities::Member, member: member
+ present member, with: ::API::Entities::Member
else
# This is to ensure back-compatibility but 400 behavior should be used
# for all validation errors in 9.0!
@@ -125,7 +126,7 @@ module API
else
::Members::DestroyService.new(source, current_user, declared_params).execute
- present member.user, with: ::API::Entities::Member, member: member
+ present member, with: ::API::Entities::Member
end
end
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 73bd4785b11..ec500838eb2 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -44,6 +44,21 @@ describe API::Members do
end
end
+ it 'avoids N+1 queries' do
+ # Establish baseline
+ get api("/#{source_type.pluralize}/#{source.id}/members", master)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/#{source_type.pluralize}/#{source.id}/members", master)
+ end
+
+ project.add_developer(create(:user))
+
+ expect do
+ get api("/#{source_type.pluralize}/#{source.id}/members", master)
+ end.not_to exceed_query_limit(control)
+ end
+
it 'does not return invitees' do
create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
--
cgit v1.2.1
From 139857d91516a42770f80ee68c2cf922337ad509 Mon Sep 17 00:00:00 2001
From: Eric Eastwood
Date: Wed, 31 Jan 2018 01:40:19 -0600
Subject: Add note about being in the `qa` directory for `bin/qa` to work
I ran into some issues when trying it out
https://gitlab.slack.com/archives/C3JJET4Q6/p1517384135000134
---
qa/README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/qa/README.md b/qa/README.md
index 3c1b61900d9..b937dc4c7a0 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -34,6 +34,9 @@ You can use GitLab QA to exercise tests on any live instance! For example, the
following call would login to a local [GDK] instance and run all specs in
`qa/specs/features`:
+First, `cd` into the `$gdk/gitlab/qa` directory.
+The `bin/qa` script expects you to be in the `qa` folder of the app.
+
```
bin/qa Test::Instance http://localhost:3000
```
--
cgit v1.2.1
From 94a3dbca33634dd4f6a1de8b14cd2a4f0a9a0abc Mon Sep 17 00:00:00 2001
From: Zeger-Jan van de Weg
Date: Tue, 30 Jan 2018 11:11:47 +0100
Subject: Gitaly Server info for admin panel
Implements the client side for gitlab-org/gitaly#819. Which is a server
info command. This checks the server version and git binairy version on
the server.
A small UI was added for administrators, so they can check the status of
the Gitaly server. This is done for each storage the monolith knows.
Because of this commit, gitlab-org/gitlab-ce!15580 is now closed. That
MR removed the Git version too, but didn't replace it with anything.
---
GITALY_SERVER_VERSION | 2 +-
Gemfile | 2 +-
Gemfile.lock | 8 +-
app/controllers/admin/gitaly_servers_controller.rb | 5 +
app/views/admin/dashboard/index.html.haml | 10 +-
app/views/admin/gitaly_servers/index.html.haml | 31 ++
changelogs/unreleased/zj-gitaly-server-info.yml | 5 +
config/routes/admin.rb | 2 +
lib/gitaly/server.rb | 43 ++
lib/gitlab/gitaly_client/server_service.rb | 16 +
locale/gitlab.pot | 492 +++++++++++++++++----
.../admin/gitaly_servers_controller_spec.rb | 15 +
spec/lib/gitaly/server_spec.rb | 30 ++
13 files changed, 555 insertions(+), 106 deletions(-)
create mode 100644 app/controllers/admin/gitaly_servers_controller.rb
create mode 100644 app/views/admin/gitaly_servers/index.html.haml
create mode 100644 changelogs/unreleased/zj-gitaly-server-info.yml
create mode 100644 lib/gitaly/server.rb
create mode 100644 lib/gitlab/gitaly_client/server_service.rb
create mode 100644 spec/controllers/admin/gitaly_servers_controller_spec.rb
create mode 100644 spec/lib/gitaly/server_spec.rb
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index b7c0622b4f4..62df9f538d8 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.74.0
+0.76.0
diff --git a/Gemfile b/Gemfile
index 05f72b6482f..1c9bd409084 100644
--- a/Gemfile
+++ b/Gemfile
@@ -406,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.78.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 1a3c8f42469..042809f918f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.78.0)
+ gitaly-proto (0.83.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -340,7 +340,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-protobuf (3.4.1.1)
+ google-protobuf (3.5.1.1-universal-darwin)
googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0)
googleauth (0.5.3)
@@ -369,7 +369,7 @@ GEM
rake
grape_logging (1.7.0)
grape
- grpc (1.8.3)
+ grpc (1.8.3-universal-darwin)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7)
@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.78.0)
+ gitaly-proto (~> 0.83.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/app/controllers/admin/gitaly_servers_controller.rb b/app/controllers/admin/gitaly_servers_controller.rb
new file mode 100644
index 00000000000..11c4dfe3d8d
--- /dev/null
+++ b/app/controllers/admin/gitaly_servers_controller.rb
@@ -0,0 +1,5 @@
+class Admin::GitalyServersController < Admin::ApplicationController
+ def index
+ @gitaly_servers = Gitaly::Server.all
+ end
+end
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 509f559c120..d251f75a8fd 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -138,19 +138,11 @@
GitLab API
%span.pull-right
= API::API::version
- %p
- Gitaly
- %span.pull-right
- = Gitlab::GitalyClient.expected_server_version
- if Gitlab.config.pages.enabled
%p
GitLab Pages
%span.pull-right
= Gitlab::Pages::VERSION
- %p
- Git
- %span.pull-right
- = Gitlab::Git.version
%p
Ruby
%span.pull-right
@@ -163,6 +155,8 @@
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
+ %p
+ = link_to "Gitaly Servers", admin_gitaly_servers_path
.row
.col-md-4
.info-well
diff --git a/app/views/admin/gitaly_servers/index.html.haml b/app/views/admin/gitaly_servers/index.html.haml
new file mode 100644
index 00000000000..231f94dc95d
--- /dev/null
+++ b/app/views/admin/gitaly_servers/index.html.haml
@@ -0,0 +1,31 @@
+- breadcrumb_title _("Gitaly Servers")
+
+%h3.page-title= _("Gitaly Servers")
+%hr
+.gitaly_servers
+ - if @gitaly_servers.any?
+ .table-holder
+ %table.table.responsive-table
+ %thead.hidden-sm.hidden-xs
+ %tr
+ %th= _("Storage")
+ %th= n_("Gitaly|Address")
+ %th= _("Server version")
+ %th= _("Git version")
+ %th= _("Up to date")
+ - @gitaly_servers.each do |server|
+ %tr
+ %td
+ = server.storage
+ %td
+ = server.address
+ %td
+ = server.server_version
+ %td
+ = server.git_binary_version
+ %td
+ = boolean_to_icon(server.up_to_date?)
+ - else
+ .empty-state
+ .text-center
+ %h4= _("No connection could be made to a Gitaly Server, please check your logs!")
diff --git a/changelogs/unreleased/zj-gitaly-server-info.yml b/changelogs/unreleased/zj-gitaly-server-info.yml
new file mode 100644
index 00000000000..cf6295f2bbc
--- /dev/null
+++ b/changelogs/unreleased/zj-gitaly-server-info.yml
@@ -0,0 +1,5 @@
+---
+title: Add Gitaly Servers admin dashboard
+merge_request:
+author:
+type: added
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index e22fb440abc..57e401c9b89 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -24,6 +24,8 @@ namespace :admin do
resource :impersonation, only: :destroy
resources :abuse_reports, only: [:index, :destroy]
+ resources :gitaly_servers, only: [:index]
+
resources :spam_logs, only: [:index, :destroy] do
member do
post :mark_as_ham
diff --git a/lib/gitaly/server.rb b/lib/gitaly/server.rb
new file mode 100644
index 00000000000..605e93022e7
--- /dev/null
+++ b/lib/gitaly/server.rb
@@ -0,0 +1,43 @@
+module Gitaly
+ class Server
+ def self.all
+ Gitlab.config.repositories.storages.keys.map { |s| Gitaly::Server.new(s) }
+ end
+
+ attr_reader :storage
+
+ def initialize(storage)
+ @storage = storage
+ end
+
+ def server_version
+ info.server_version
+ end
+
+ def git_binary_version
+ info.git_version
+ end
+
+ def up_to_date?
+ server_version == Gitlab::GitalyClient.expected_server_version
+ end
+
+ def address
+ Gitlab::GitalyClient.address(@storage)
+ rescue RuntimeError => e
+ "Error getting the address: #{e.message}"
+ end
+
+ private
+
+ def info
+ @info ||=
+ begin
+ Gitlab::GitalyClient::ServerService.new(@storage).info
+ rescue GRPC::Unavailable, GRPC::GRPC::DeadlineExceeded
+ # This will show the server as being out of date
+ Gitaly::ServerInfoResponse.new(git_version: '', server_version: '')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/server_service.rb b/lib/gitlab/gitaly_client/server_service.rb
new file mode 100644
index 00000000000..2e1076d1f66
--- /dev/null
+++ b/lib/gitlab/gitaly_client/server_service.rb
@@ -0,0 +1,16 @@
+module Gitlab
+ module GitalyClient
+ # Meant for extraction of server data, and later maybe to perform misc task
+ #
+ # Not meant for connection logic, look in Gitlab::GitalyClient
+ class ServerService
+ def initialize(storage)
+ @storage = storage
+ end
+
+ def info
+ GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new)
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 74d76caf47d..94458d60e01 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-12-05 20:31+0100\n"
-"PO-Revision-Date: 2017-12-05 20:31+0100\n"
+"POT-Creation-Date: 2018-01-30 14:59+0100\n"
+"PO-Revision-Date: 2018-01-30 14:59+0100\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"Language: \n"
@@ -23,17 +23,27 @@ msgid_plural "%d commits"
msgstr[0] ""
msgstr[1] ""
+msgid "%d issue"
+msgid_plural "%d issues"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d layer"
msgid_plural "%d layers"
msgstr[0] ""
msgstr[1] ""
+msgid "%d merge request"
+msgid_plural "%d merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
-msgid "%{commit_author_link} committed %{commit_timeago}"
+msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
msgid "%{count} participant"
@@ -47,9 +57,6 @@ msgstr ""
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
msgstr ""
-msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
-msgstr ""
-
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
msgstr ""
@@ -117,6 +124,21 @@ msgstr ""
msgid "Add new directory"
msgstr ""
+msgid "AdminArea|Stop all jobs"
+msgstr ""
+
+msgid "AdminArea|Stop all jobs?"
+msgstr ""
+
+msgid "AdminArea|Stop jobs"
+msgstr ""
+
+msgid "AdminArea|Stopping jobs failed"
+msgstr ""
+
+msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running."
+msgstr ""
+
msgid "AdminHealthPageLink|health page"
msgstr ""
@@ -126,12 +148,18 @@ msgstr ""
msgid "All"
msgstr ""
+msgid "An error occurred previewing the blob"
+msgstr ""
+
msgid "An error occurred when toggling the notification subscription"
msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
+msgid "An error occurred while retrieving diff"
+msgstr ""
+
msgid "An error occurred. Please try again."
msgstr ""
@@ -156,9 +184,6 @@ msgstr ""
msgid "Are you sure you want to discard your changes?"
msgstr ""
-msgid "Are you sure you want to leave this group?"
-msgstr ""
-
msgid "Are you sure you want to reset registration token?"
msgstr ""
@@ -186,13 +211,13 @@ msgstr ""
msgid "Author"
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
+msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
+msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
+msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
msgstr ""
msgid "AutoDevOps|Auto DevOps (Beta)"
@@ -216,6 +241,9 @@ msgstr ""
msgid "Available"
msgstr ""
+msgid "Avatar will be removed. Are you sure?"
+msgstr ""
+
msgid "Branch"
msgid_plural "Branches"
msgstr[0] ""
@@ -359,15 +387,24 @@ msgstr ""
msgid "ChangeTypeAction|Revert"
msgstr ""
+msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
+msgstr ""
+
msgid "Changelog"
msgstr ""
+msgid "Changes are shown as if the source revision was being merged into the target revision."
+msgstr ""
+
msgid "Charts"
msgstr ""
msgid "Chat"
msgstr ""
+msgid "Check interval"
+msgstr ""
+
msgid "Checking %{text} availability…"
msgstr ""
@@ -380,6 +417,15 @@ msgstr ""
msgid "Cherry-pick this merge request"
msgstr ""
+msgid "Choose File ..."
+msgstr ""
+
+msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
+msgstr ""
+
+msgid "Choose file..."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr ""
@@ -437,6 +483,9 @@ msgstr ""
msgid "CircuitBreakerApiLink|circuitbreaker api"
msgstr ""
+msgid "Click to expand text"
+msgstr ""
+
msgid "Clone repository"
msgstr ""
@@ -446,27 +495,24 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your cluster"
msgstr ""
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}"
-msgstr ""
-
msgid "ClusterIntegration|API URL"
msgstr ""
-msgid "ClusterIntegration|Active"
-msgstr ""
-
msgid "ClusterIntegration|Add an existing cluster"
msgstr ""
msgid "ClusterIntegration|Add cluster"
msgstr ""
-msgid "ClusterIntegration|All"
+msgid "ClusterIntegration|Advanced options on this cluster's integration"
msgstr ""
msgid "ClusterIntegration|Applications"
msgstr ""
+msgid "ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster."
+msgstr ""
+
msgid "ClusterIntegration|CA Certificate"
msgstr ""
@@ -476,6 +522,9 @@ msgstr ""
msgid "ClusterIntegration|Choose how to set up cluster integration"
msgstr ""
+msgid "ClusterIntegration|Choose which of your project's environments will use this cluster."
+msgstr ""
+
msgid "ClusterIntegration|Cluster"
msgstr ""
@@ -506,6 +555,12 @@ msgstr ""
msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
msgstr ""
+msgid "ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project"
+msgstr ""
+
+msgid "ClusterIntegration|Control how your cluster integrates with GitLab"
+msgstr ""
+
msgid "ClusterIntegration|Copy API URL"
msgstr ""
@@ -518,7 +573,7 @@ msgstr ""
msgid "ClusterIntegration|Copy cluster name"
msgstr ""
-msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab"
+msgid "ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab"
msgstr ""
msgid "ClusterIntegration|Create cluster"
@@ -530,19 +585,16 @@ msgstr ""
msgid "ClusterIntegration|Create on GKE"
msgstr ""
-msgid "ClusterIntegration|Enable cluster integration"
-msgstr ""
-
msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|Enter the details for your cluster"
msgstr ""
-msgid "ClusterIntegration|Environment pattern"
+msgid "ClusterIntegration|Environment scope"
msgstr ""
-msgid "ClusterIntegration|GKE pricing"
+msgid "ClusterIntegration|GitLab Integration"
msgstr ""
msgid "ClusterIntegration|GitLab Runner"
@@ -560,18 +612,12 @@ msgstr ""
msgid "ClusterIntegration|Helm Tiller"
msgstr ""
-msgid "ClusterIntegration|Inactive"
-msgstr ""
-
msgid "ClusterIntegration|Ingress"
msgstr ""
msgid "ClusterIntegration|Install"
msgstr ""
-msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}"
-msgstr ""
-
msgid "ClusterIntegration|Installed"
msgstr ""
@@ -581,27 +627,27 @@ msgstr ""
msgid "ClusterIntegration|Integrate cluster automation"
msgstr ""
+msgid "ClusterIntegration|Integration status"
+msgstr ""
+
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
msgstr ""
msgid "ClusterIntegration|Learn more about Clusters"
msgstr ""
-msgid "ClusterIntegration|Machine type"
+msgid "ClusterIntegration|Learn more about environments"
msgstr ""
-msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters"
+msgid "ClusterIntegration|Machine type"
msgstr ""
-msgid "ClusterIntegration|Manage cluster integration on your GitLab project"
+msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters"
msgstr ""
msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}"
msgstr ""
-msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate"
-msgstr ""
-
msgid "ClusterIntegration|Note:"
msgstr ""
@@ -614,12 +660,6 @@ msgstr ""
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr ""
-msgid "ClusterIntegration|Problem setting up the cluster"
-msgstr ""
-
-msgid "ClusterIntegration|Problem setting up the clusters list"
-msgstr ""
-
msgid "ClusterIntegration|Project ID"
msgstr ""
@@ -629,6 +669,9 @@ msgstr ""
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
+msgid "ClusterIntegration|Prometheus"
+msgstr ""
+
msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration."
msgstr ""
@@ -638,7 +681,7 @@ msgstr ""
msgid "ClusterIntegration|Remove integration"
msgstr ""
-msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine."
+msgid "ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster."
msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
@@ -674,9 +717,6 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
-msgid "ClusterIntegration|There are no clusters to show"
-msgstr ""
-
msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below"
msgstr ""
@@ -698,6 +738,9 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
+msgid "ClusterIntegration|check the pricing here"
+msgstr ""
+
msgid "ClusterIntegration|cluster"
msgstr ""
@@ -745,15 +788,42 @@ msgstr ""
msgid "Commits feed"
msgstr ""
+msgid "Commits|An error occurred while fetching merge requests data."
+msgstr ""
+
msgid "Commits|History"
msgstr ""
+msgid "Commits|No related merge requests found"
+msgstr ""
+
msgid "Committed by"
msgstr ""
msgid "Compare"
msgstr ""
+msgid "Compare Git revisions"
+msgstr ""
+
+msgid "Compare Revisions"
+msgstr ""
+
+msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
+msgstr ""
+
+msgid "CompareBranches|Compare"
+msgstr ""
+
+msgid "CompareBranches|Source"
+msgstr ""
+
+msgid "CompareBranches|Target"
+msgstr ""
+
+msgid "CompareBranches|There isn't anything to compare."
+msgstr ""
+
msgid "Container Registry"
msgstr ""
@@ -805,6 +875,9 @@ msgstr ""
msgid "Contributors"
msgstr ""
+msgid "ContributorsPage|%{startDate} – %{endDate}"
+msgstr ""
+
msgid "ContributorsPage|Building repository graph."
msgstr ""
@@ -874,9 +947,6 @@ msgstr ""
msgid "Cycle Analytics"
msgstr ""
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
-msgstr ""
-
msgid "CycleAnalyticsStage|Code"
msgstr ""
@@ -930,6 +1000,9 @@ msgstr ""
msgid "Details"
msgstr ""
+msgid "Diffs|No file name available"
+msgstr ""
+
msgid "Directory name"
msgstr ""
@@ -996,9 +1069,6 @@ msgstr ""
msgid "Environments|Environments"
msgstr ""
-msgid "Environments|Environments are places where code gets deployed, such as staging or production."
-msgstr ""
-
msgid "Environments|Job"
msgstr ""
@@ -1029,6 +1099,9 @@ msgstr ""
msgid "Environments|You don't have any environments right now."
msgstr ""
+msgid "Error fetching refs"
+msgstr ""
+
msgid "Error occurred when toggling the notification subscription"
msgstr ""
@@ -1121,12 +1194,21 @@ msgstr ""
msgid "GPG Keys"
msgstr ""
+msgid "Generate a default set of labels"
+msgstr ""
+
msgid "Git storage health information has been reset"
msgstr ""
+msgid "Git version"
+msgstr ""
+
msgid "GitLab Runner section"
msgstr ""
+msgid "Gitaly Servers"
+msgstr ""
+
msgid "Go to your fork"
msgstr ""
@@ -1172,10 +1254,7 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
-msgid "GroupsTreeRole|as"
-msgstr ""
-
-msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?"
+msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?"
msgstr ""
msgid "GroupsTree|Create a project in this group."
@@ -1223,6 +1302,11 @@ msgstr ""
msgid "HealthCheck|Unhealthy"
msgstr ""
+msgid "Hide value"
+msgid_plural "Hide values"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "History"
msgstr ""
@@ -1235,6 +1319,9 @@ msgstr ""
msgid "Install a Runner compatible with GitLab CI"
msgstr ""
+msgid "Interested parties can even contribute by pushing commits if they want to."
+msgstr ""
+
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr ""
@@ -1256,6 +1343,9 @@ msgstr ""
msgid "Issues"
msgstr ""
+msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
+msgstr ""
+
msgid "Jan"
msgstr ""
@@ -1283,6 +1373,9 @@ msgstr ""
msgid "Labels"
msgstr ""
+msgid "Labels can be applied to issues and merge requests to categorize them."
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] ""
@@ -1327,10 +1420,8 @@ msgstr ""
msgid "Leave project"
msgstr ""
-msgid "Limited to showing %d event at most"
-msgid_plural "Limited to showing %d events at most"
-msgstr[0] ""
-msgstr[1] ""
+msgid "Loading the GitLab IDE..."
+msgstr ""
msgid "Lock"
msgstr ""
@@ -1368,9 +1459,27 @@ msgstr ""
msgid "Merge request"
msgstr ""
+msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
+msgstr ""
+
+msgid "Merged"
+msgstr ""
+
msgid "Messages"
msgstr ""
+msgid "Milestones|Delete milestone"
+msgstr ""
+
+msgid "Milestones|Delete milestone %{milestoneTitle}?"
+msgstr ""
+
+msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
+msgstr ""
+
+msgid "Milestones|Milestone %{milestoneTitle} was not found"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -1409,6 +1518,9 @@ msgstr ""
msgid "New issue"
msgstr ""
+msgid "New label"
+msgstr ""
+
msgid "New merge request"
msgstr ""
@@ -1427,7 +1539,10 @@ msgstr ""
msgid "New tag"
msgstr ""
-msgid "No container images stored for this project. Add one by following the instructions above."
+msgid "No connection could be made to a Gitaly Server, please check your logs!"
+msgstr ""
+
+msgid "No file chosen"
msgstr ""
msgid "No repository"
@@ -1505,6 +1620,12 @@ msgstr ""
msgid "Notifications"
msgstr ""
+msgid "Notifications off"
+msgstr ""
+
+msgid "Notifications on"
+msgstr ""
+
msgid "Nov"
msgstr ""
@@ -1514,9 +1635,6 @@ msgstr ""
msgid "Number of access attempts"
msgstr ""
-msgid "Number of failures before backing off"
-msgstr ""
-
msgid "Oct"
msgstr ""
@@ -1559,9 +1677,6 @@ msgstr ""
msgid "Password"
msgstr ""
-msgid "People without permission will never get a notification and won\\'t be able to comment."
-msgstr ""
-
msgid "Pipeline"
msgstr ""
@@ -1646,6 +1761,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines|Build with confidence"
+msgstr ""
+
+msgid "Pipelines|Get started with Pipelines"
+msgstr ""
+
msgid "Pipeline|all"
msgstr ""
@@ -1658,6 +1779,15 @@ msgstr ""
msgid "Pipeline|with stages"
msgstr ""
+msgid "Play"
+msgstr ""
+
+msgid "Please enable billing for one of your projects to be able to create a cluster , then try again."
+msgstr ""
+
+msgid "Please solve the reCAPTCHA"
+msgstr ""
+
msgid "Preferences"
msgstr ""
@@ -1721,6 +1851,15 @@ msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project avatar"
+msgstr ""
+
+msgid "Project avatar in repository: %{link}"
+msgstr ""
+
+msgid "Project cache successfully reset."
+msgstr ""
+
msgid "Project details"
msgstr ""
@@ -1760,12 +1899,6 @@ msgstr ""
msgid "ProjectNetworkGraph|Graph"
msgstr ""
-msgid "ProjectSettings|Immediately run a pipeline on the default branch"
-msgstr ""
-
-msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript"
-msgstr ""
-
msgid "Projects"
msgstr ""
@@ -1844,6 +1977,9 @@ msgstr ""
msgid "RefSwitcher|Tags"
msgstr ""
+msgid "Register / Sign In"
+msgstr ""
+
msgid "Related Commits"
msgstr ""
@@ -1865,6 +2001,9 @@ msgstr ""
msgid "Remind later"
msgstr ""
+msgid "Remove avatar"
+msgstr ""
+
msgid "Remove project"
msgstr ""
@@ -1883,6 +2022,11 @@ msgstr ""
msgid "Reset runners registration token"
msgstr ""
+msgid "Reveal value"
+msgid_plural "Reveal values"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Revert this commit"
msgstr ""
@@ -1892,9 +2036,6 @@ msgstr ""
msgid "SSH Keys"
msgstr ""
-msgid "Save"
-msgstr ""
-
msgid "Save changes"
msgstr ""
@@ -1916,9 +2057,6 @@ msgstr ""
msgid "Seconds before reseting failure information"
msgstr ""
-msgid "Seconds to wait after a storage failure"
-msgstr ""
-
msgid "Seconds to wait for a storage access attempt"
msgstr ""
@@ -1928,6 +2066,9 @@ msgstr ""
msgid "Select a timezone"
msgstr ""
+msgid "Select branch/tag"
+msgstr ""
+
msgid "Select target branch"
msgstr ""
@@ -1937,6 +2078,9 @@ msgstr ""
msgid "September"
msgstr ""
+msgid "Server version"
+msgstr ""
+
msgid "Service Templates"
msgstr ""
@@ -1975,7 +2119,7 @@ msgstr ""
msgid "Something went wrong on our end."
msgstr ""
-msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}"
+msgid "Something went wrong when toggling the button"
msgstr ""
msgid "Something went wrong while fetching the projects."
@@ -1984,6 +2128,9 @@ msgstr ""
msgid "Something went wrong while fetching the registry list."
msgstr ""
+msgid "Something went wrong. Please try again."
+msgstr ""
+
msgid "Sort by"
msgstr ""
@@ -2104,10 +2251,10 @@ msgstr ""
msgid "Stopped"
msgstr ""
-msgid "Subgroups"
+msgid "Storage"
msgstr ""
-msgid "Subscribe"
+msgid "Subgroups"
msgstr ""
msgid "Switch branch/tag"
@@ -2199,7 +2346,10 @@ msgstr ""
msgid "Team"
msgstr ""
-msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold"
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
+msgstr ""
+
+msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
msgstr ""
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
@@ -2214,10 +2364,10 @@ msgstr ""
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
msgstr ""
-msgid "The number of attempts GitLab will make to access a storage."
+msgid "The maximum file size allowed is 200KB."
msgstr ""
-msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host"
+msgid "The number of attempts GitLab will make to access a storage."
msgstr ""
msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
@@ -2226,9 +2376,6 @@ msgstr ""
msgid "The phase of the development lifecycle."
msgstr ""
-msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
-msgstr ""
-
msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
msgstr ""
@@ -2259,16 +2406,25 @@ msgstr ""
msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised."
msgstr ""
+msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
+msgstr ""
+
msgid "The time taken by each data entry gathered by that stage."
msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
+msgid "There are no issues to show"
+msgstr ""
+
+msgid "There are no merge requests to show"
+msgstr ""
+
msgid "There are problems accessing Git storage: "
msgstr ""
-msgid "This branch has changed since you started editing. Would you like to create a new branch?"
+msgid "This directory"
msgstr ""
msgid "This is a confidential issue."
@@ -2283,12 +2439,36 @@ msgstr ""
msgid "This issue is locked."
msgstr ""
+msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+msgstr ""
+
+msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
+msgstr ""
+
+msgid "This job has not been triggered yet"
+msgstr ""
+
+msgid "This job has not started yet"
+msgstr ""
+
+msgid "This job is in pending state and is waiting to be picked by a runner"
+msgstr ""
+
+msgid "This job requires a manual action"
+msgstr ""
+
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr ""
msgid "This merge request is locked."
msgstr ""
+msgid "This project"
+msgstr ""
+
+msgid "This repository"
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -2437,6 +2617,12 @@ msgstr[1] ""
msgid "Time|s"
msgstr ""
+msgid "ToggleButton|Toggle Status: OFF"
+msgstr ""
+
+msgid "ToggleButton|Toggle Status: ON"
+msgstr ""
+
msgid "Total Time"
msgstr ""
@@ -2446,6 +2632,12 @@ msgstr ""
msgid "Total test time for all commits/merges"
msgstr ""
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Unable to reset project cache."
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -2455,7 +2647,7 @@ msgstr ""
msgid "Unstar"
msgstr ""
-msgid "Unsubscribe"
+msgid "Up to date"
msgstr ""
msgid "Upload New File"
@@ -2464,6 +2656,9 @@ msgstr ""
msgid "Upload file"
msgstr ""
+msgid "Upload new avatar"
+msgstr ""
+
msgid "UploadLink|click to upload"
msgstr ""
@@ -2497,10 +2692,13 @@ msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
+msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again."
+msgstr ""
+
msgid "We don't have enough data to show this stage."
msgstr ""
-msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable"
+msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr ""
msgid "Wiki"
@@ -2623,9 +2821,15 @@ msgstr ""
msgid "You are on a read-only GitLab instance."
msgstr ""
+msgid "You can also star a label to make it a priority label."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
+msgid "You can only edit files when you are on a branch"
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
@@ -2662,6 +2866,9 @@ msgstr ""
msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile"
msgstr ""
+msgid "You'll need to use different branch names to get a valid comparison."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
@@ -2682,12 +2889,113 @@ msgid_plural "days"
msgstr[0] ""
msgstr[1] ""
+msgid "merge request"
+msgid_plural "merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "mrWidget|Cancel automatic merge"
+msgstr ""
+
+msgid "mrWidget|Checking ability to merge automatically"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick"
+msgstr ""
+
+msgid "mrWidget|Cherry-pick this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Closed by"
+msgstr ""
+
+msgid "mrWidget|Merge"
+msgstr ""
+
+msgid "mrWidget|Merge failed."
+msgstr ""
+
+msgid "mrWidget|Merge locally"
+msgstr ""
+
+msgid "mrWidget|Merged by"
+msgstr ""
+
+msgid "mrWidget|Refresh"
+msgstr ""
+
+msgid "mrWidget|Refresh now"
+msgstr ""
+
+msgid "mrWidget|Refreshing now"
+msgstr ""
+
+msgid "mrWidget|Remove Source Branch"
+msgstr ""
+
+msgid "mrWidget|Remove source branch"
+msgstr ""
+
+msgid "mrWidget|Resolve conflicts"
+msgstr ""
+
+msgid "mrWidget|Revert"
+msgstr ""
+
+msgid "mrWidget|Revert this merge request in a new merge request"
+msgstr ""
+
+msgid "mrWidget|Set by"
+msgstr ""
+
+msgid "mrWidget|The changes were merged into"
+msgstr ""
+
+msgid "mrWidget|The changes were not merged into"
+msgstr ""
+
+msgid "mrWidget|The changes will be merged into"
+msgstr ""
+
+msgid "mrWidget|The source branch has been removed"
+msgstr ""
+
+msgid "mrWidget|The source branch is being removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will be removed"
+msgstr ""
+
+msgid "mrWidget|The source branch will not be removed"
+msgstr ""
+
+msgid "mrWidget|There are merge conflicts"
+msgstr ""
+
+msgid "mrWidget|This merge request failed to be merged automatically"
+msgstr ""
+
+msgid "mrWidget|This merge request is in the process of being merged"
+msgstr ""
+
+msgid "mrWidget|This project is archived, write access has been disabled"
+msgstr ""
+
+msgid "mrWidget|You can remove source branch now"
+msgstr ""
+
+msgid "mrWidget|to be merged automatically when the pipeline succeeds"
+msgstr ""
+
msgid "new merge request"
msgstr ""
msgid "notification emails"
msgstr ""
+msgid "or"
+msgstr ""
+
msgid "parent"
msgid_plural "parents"
msgstr[0] ""
diff --git a/spec/controllers/admin/gitaly_servers_controller_spec.rb b/spec/controllers/admin/gitaly_servers_controller_spec.rb
new file mode 100644
index 00000000000..b7378aa37d0
--- /dev/null
+++ b/spec/controllers/admin/gitaly_servers_controller_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Admin::GitalyServersController do
+ describe '#index' do
+ before do
+ sign_in(create(:admin))
+ end
+
+ it 'shows the gitaly servers page' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+end
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
new file mode 100644
index 00000000000..ed5d56e91d4
--- /dev/null
+++ b/spec/lib/gitaly/server_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitaly::Server do
+ describe '.all' do
+ let(:storages) { Gitlab.config.repositories.storages }
+
+ it 'includes all storages' do
+ expect(storages.count).to eq(described_class.all.count)
+ expect(storages.keys).to eq(described_class.all.map(&:storage))
+ end
+ end
+
+ subject { described_class.all.first }
+
+ it { is_expected.to respond_to(:server_version) }
+ it { is_expected.to respond_to(:git_binary_version) }
+ it { is_expected.to respond_to(:up_to_date?) }
+ it { is_expected.to respond_to(:address) }
+
+ describe 'request memoization' do
+ context 'when requesting multiple properties', :request_store do
+ it 'uses memoization for the info request' do
+ expect do
+ subject.server_version
+ subject.up_to_date?
+ end.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+ end
+end
--
cgit v1.2.1
From f165bda4ae2a92528e6f4da25825c1e441094c54 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Wed, 31 Jan 2018 09:27:30 +0000
Subject: fixed failing specs
---
app/assets/javascripts/labels_select.js | 12 ++++----
app/assets/javascripts/merge_request_tabs.js | 2 +-
spec/javascripts/issue_spec.js | 4 +--
spec/javascripts/job_spec.js | 8 ++---
spec/javascripts/labels_issue_sidebar_spec.js | 43 +++++++++++++++++----------
spec/javascripts/merge_request_tabs_spec.js | 8 ++---
6 files changed, 43 insertions(+), 34 deletions(-)
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 06b8333db27..5ecf81ad11d 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -102,28 +102,28 @@ export default class LabelsSelect {
}
$value.removeAttr('style').html(template);
$sidebarCollapsedValue.text(labelCount);
-
+
if (data.labels.length) {
labelTitles = data.labels.map(function(label) {
return label.title;
});
-
+
if (labelTitles.length > 5) {
labelTitles = labelTitles.slice(0, 5);
labelTitles.push('and ' + (data.labels.length - 5) + ' more');
}
-
+
labelTooltipTitle = labelTitles.join(', ');
}
else {
labelTooltipTitle = '';
$sidebarLabelTooltip.tooltip('destroy');
}
-
+
$sidebarLabelTooltip
.attr('title', labelTooltipTitle)
.tooltip('fixTitle');
-
+
$('.has-tooltip', $value).tooltip({
container: 'body'
});
@@ -168,7 +168,7 @@ export default class LabelsSelect {
data = extraData.concat(data);
}
}
-
+
callback(data);
if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove();
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index f69506a0471..6151e90aa04 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -246,7 +246,7 @@ export default class MergeRequestTabs {
return;
}
- this.toggleLoading(true)
+ this.toggleLoading(true);
axios.get(`${source}.json`)
.then(({ data }) => {
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 2da7cede510..7eaaa0ff027 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,5 +1,5 @@
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
-import MockAdaptor from 'axios-mock-adapter';
+import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Issue from '~/issue';
import '~/lib/utils/text_utility';
@@ -138,7 +138,7 @@ describe('Issue', function() {
this.issueStateDeferred = new jQuery.Deferred();
this.canCreateBranchDeferred = new jQuery.Deferred();
- mock = new MockAdaptor(axios);
+ mock = new MockAdapter(axios);
spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this));
});
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index c3d8d821ac4..03b58e9c1d0 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -1,4 +1,4 @@
-import MockAdaptor from 'axios-mock-adapter';
+import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import * as urlUtils from '~/lib/utils/url_utility';
@@ -22,11 +22,9 @@ describe('Job', () => {
spyOn(urlUtils, 'visitUrl');
- mock = new MockAdaptor(axios);
+ mock = new MockAdapter(axios);
- mock.onGet(new RegExp(`${JOB_URL}/trace.json?(.*)`)).reply(() => {
- return [200, response];
- });
+ mock.onGet(new RegExp(`${JOB_URL}/trace.json?(.*)`)).reply(() => [200, response]);
});
afterEach(() => {
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index a197b35f6fb..7d992f62f64 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new */
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import IssuableContext from '~/issuable_context';
import LabelsSelect from '~/labels_select';
@@ -10,35 +12,44 @@ import '~/users_select';
(() => {
let saveLabelCount = 0;
+ let mock;
+
describe('Issue dropdown sidebar', () => {
preloadFixtures('static/issue_sidebar_label.html.raw');
beforeEach(() => {
loadFixtures('static/issue_sidebar_label.html.raw');
+
+ mock = new MockAdapter(axios);
+
new IssuableContext('{"id":1,"name":"Administrator","username":"root"}');
new LabelsSelect();
- spyOn(jQuery, 'ajax').and.callFake((req) => {
- const d = $.Deferred();
- let LABELS_DATA = [];
+ mock.onGet('/root/test/labels.json').reply(() => {
+ const labels = Array(10).fill().map((_, i) => ({
+ id: i,
+ title: `test ${i}`,
+ color: '#5CB85C',
+ }));
- if (req.url === '/root/test/labels.json') {
- for (let i = 0; i < 10; i += 1) {
- LABELS_DATA.push({ id: i, title: `test ${i}`, color: '#5CB85C' });
- }
- } else if (req.url === '/root/test/issues/2.json') {
- const tmp = [];
- for (let i = 0; i < saveLabelCount; i += 1) {
- tmp.push({ id: i, title: `test ${i}`, color: '#5CB85C' });
- }
- LABELS_DATA = { labels: tmp };
- }
+ return [200, labels];
+ });
+
+ mock.onPut('/root/test/issues/2.json').reply(() => {
+ const labels = Array(saveLabelCount).fill().map((_, i) => ({
+ id: i,
+ title: `test ${i}`,
+ color: '#5CB85C',
+ }));
- d.resolve(LABELS_DATA);
- return d.promise();
+ return [200, { labels }];
});
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('changes collapsed tooltip when changing labels when less than 5', (done) => {
saveLabelCount = 5;
$('.edit-link').get(0).click();
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 116c4e8f0e4..fda24db98b4 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,5 +1,5 @@
/* eslint-disable no-var, comma-dangle, object-shorthand */
-import MockAdaptor from 'axios-mock-adapter';
+import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import MergeRequestTabs from '~/merge_request_tabs';
@@ -218,7 +218,7 @@ import 'vendor/jquery.scrollTo';
let mock;
beforeEach(function () {
- mock = new MockAdaptor(axios);
+ mock = new MockAdapter(axios);
mock.onGet(/(.*)\/diffs\.json/).reply(200, {
data: { html: '' },
});
@@ -344,7 +344,7 @@ import 'vendor/jquery.scrollTo';
.attr('href')
.replace('#', '');
- mock = new MockAdaptor(axios);
+ mock = new MockAdapter(axios);
mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
});
@@ -410,7 +410,7 @@ import 'vendor/jquery.scrollTo';
.attr('href')
.replace('#', '');
- mock = new MockAdaptor(axios);
+ mock = new MockAdapter(axios);
mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
});
--
cgit v1.2.1
From d58ff9433db2e737329d7aea436d086a133bdfe0 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Wed, 31 Jan 2018 09:29:29 +0000
Subject: Converted milestone.js to axios
---
app/assets/javascripts/milestone.js | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index dd6c6b854bc..b1d74250dfd 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,4 +1,5 @@
-import Flash from './flash';
+import axios from './lib/utils/axios_utils';
+import flash from './flash';
export default class Milestone {
constructor() {
@@ -33,15 +34,12 @@ export default class Milestone {
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
- $.ajax({
- url: endpoint,
- dataType: 'JSON',
- })
- .fail(() => new Flash('Error loading milestone tab'))
- .done((data) => {
- $(tabElId).html(data.html);
- $target.addClass('is-loaded');
- });
+ axios.get(endpoint)
+ .then(({ data }) => {
+ $(tabElId).html(data.html);
+ $target.addClass('is-loaded');
+ })
+ .catch(() => flash('Error loading milestone tab'));
}
}
}
--
cgit v1.2.1
From 3ae2d90b49f727096fb7848bb904951db2179c68 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Wed, 31 Jan 2018 09:34:25 +0000
Subject: Converted milestone_select.js to axios
---
app/assets/javascripts/milestone_select.js | 119 ++++++++++++++---------------
1 file changed, 58 insertions(+), 61 deletions(-)
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 0e854295fe3..6581be606eb 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -2,6 +2,7 @@
/* global Issuable */
/* global ListMilestone */
import _ from 'underscore';
+import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
export default class MilestoneSelect {
@@ -52,48 +53,47 @@ export default class MilestoneSelect {
}
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
- data: (term, callback) => $.ajax({
- url: milestonesUrl
- }).done((data) => {
- const extraOptions = [];
- if (showAny) {
- extraOptions.push({
- id: 0,
- name: '',
- title: 'Any Milestone'
- });
- }
- if (showNo) {
- extraOptions.push({
- id: -1,
- name: 'No Milestone',
- title: 'No Milestone'
- });
- }
- if (showUpcoming) {
- extraOptions.push({
- id: -2,
- name: '#upcoming',
- title: 'Upcoming'
- });
- }
- if (showStarted) {
- extraOptions.push({
- id: -3,
- name: '#started',
- title: 'Started'
- });
- }
- if (extraOptions.length) {
- extraOptions.push('divider');
- }
+ data: (term, callback) => axios.get(milestonesUrl)
+ .then(({ data }) => {
+ const extraOptions = [];
+ if (showAny) {
+ extraOptions.push({
+ id: 0,
+ name: '',
+ title: 'Any Milestone'
+ });
+ }
+ if (showNo) {
+ extraOptions.push({
+ id: -1,
+ name: 'No Milestone',
+ title: 'No Milestone'
+ });
+ }
+ if (showUpcoming) {
+ extraOptions.push({
+ id: -2,
+ name: '#upcoming',
+ title: 'Upcoming'
+ });
+ }
+ if (showStarted) {
+ extraOptions.push({
+ id: -3,
+ name: '#started',
+ title: 'Started'
+ });
+ }
+ if (extraOptions.length) {
+ extraOptions.push('divider');
+ }
- callback(extraOptions.concat(data));
- if (showMenuAbove) {
- $dropdown.data('glDropdown').positionMenuAbove();
- }
- $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
- }),
+ callback(extraOptions.concat(data));
+ if (showMenuAbove) {
+ $dropdown.data('glDropdown').positionMenuAbove();
+ }
+ $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
+ }),
renderRow: milestone => `
- Mentions
+ {{ s__("mrWidget|Mentions") }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 6d3573de76c..edf67fcd0a7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -63,8 +63,7 @@ export default {
return this.mr.hasCI;
},
shouldRenderRelatedLinks() {
- const { closing, mentioned, assignToMe } = this.mr.relatedLinks;
- return (closing || mentioned || assignToMe) && !this.mr.isNothingToMergeState;
+ return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
},
shouldRenderDeployments() {
return this.mr.deployments.length;
--
cgit v1.2.1
From 28bbb4cb47ebb8669643e8fad34b75ea34f18e36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kim=20=22BKC=22=20Carlb=C3=A4cker?=
Date: Fri, 26 Jan 2018 09:00:22 +0100
Subject: Migrate Gitlab::Git::Repository#write_config to Gitaly
- Add tests for Repository#write_config
---
GITALY_SERVER_VERSION | 2 +-
lib/gitlab/git.rb | 1 +
lib/gitlab/git/repository.rb | 14 +++++++++-
lib/gitlab/gitaly_client/repository_service.rb | 13 +++++++++
spec/lib/gitlab/git/repository_spec.rb | 38 ++++++++++++++++++++++++++
5 files changed, 66 insertions(+), 2 deletions(-)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 62df9f538d8..4a7076db09a 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.76.0
+0.77.0
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 71647099f83..9e8caa259d2 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -6,6 +6,7 @@ module Gitlab
CommandError = Class.new(StandardError)
CommitError = Class.new(StandardError)
+ OSError = Class.new(StandardError)
class << self
include Gitlab::EncodingHelper
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 8137c582c0f..8ba12808c10 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1311,7 +1311,15 @@ module Gitlab
# rubocop:enable Metrics/ParameterLists
def write_config(full_path:)
- rugged.config['gitlab.fullpath'] = full_path if full_path.present?
+ return unless full_path.present?
+
+ gitaly_migrate(:write_config) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.write_config(full_path: full_path)
+ else
+ rugged_write_config(full_path: full_path)
+ end
+ end
end
def gitaly_repository
@@ -1451,6 +1459,10 @@ module Gitlab
end
end
+ def rugged_write_config(full_path:)
+ rugged.config['gitlab.fullpath'] = full_path
+ end
+
def shell_write_ref(ref_path, ref, old_ref)
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 7adf32af209..60706b4f0d8 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -219,6 +219,19 @@ module Gitlab
true
end
+
+ def write_config(full_path:)
+ request = Gitaly::WriteConfigRequest.new(repository: @gitaly_repo, full_path: full_path)
+ response = GitalyClient.call(
+ @storage,
+ :repository_service,
+ :write_config,
+ request,
+ timeout: GitalyClient.fast_timeout
+ )
+
+ raise Gitlab::Git::OSError.new(response.error) unless response.error.empty?
+ end
end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index bf01e6ef8e8..6206e9bb0fb 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1790,6 +1790,44 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#write_config' do
+ before do
+ repository.rugged.config["gitlab.fullpath"] = repository.path
+ end
+
+ shared_examples 'writing repo config' do
+ context 'is given a path' do
+ it 'writes it to disk' do
+ repository.write_config(full_path: "not-the/real-path.git")
+
+ config = File.read(File.join(repository.path, "config"))
+
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = not-the/real-path.git")
+ end
+ end
+
+ context 'it is given an empty path' do
+ it 'does not write it to disk' do
+ repository.write_config(full_path: "")
+
+ config = File.read(File.join(repository.path, "config"))
+
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = #{repository.path}")
+ end
+ end
+ end
+
+ context "when gitaly_write_config is enabled" do
+ it_behaves_like "writing repo config"
+ end
+
+ context "when gitaly_write_config is disabled", :disable_gitaly do
+ it_behaves_like "writing repo config"
+ end
+ end
+
describe '#merge' do
let(:repository) do
Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
--
cgit v1.2.1
From 7bd66940d9f1d79e7277dedfa6cb2749857660d6 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Wed, 31 Jan 2018 15:12:10 +0000
Subject: fixed eslint
---
spec/javascripts/lib/utils/ajax_cache_spec.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/javascripts/lib/utils/ajax_cache_spec.js
index be137ad8167..7603400b55e 100644
--- a/spec/javascripts/lib/utils/ajax_cache_spec.js
+++ b/spec/javascripts/lib/utils/ajax_cache_spec.js
@@ -88,8 +88,7 @@ describe('AjaxCache', () => {
});
});
- fdescribe('retrieve', () => {
- let ajaxSpy;
+ describe('retrieve', () => {
let mock;
beforeEach(() => {
--
cgit v1.2.1
From 406e10b89c41db709217367d8093d80ad00b02df Mon Sep 17 00:00:00 2001
From: James Lopez
Date: Wed, 31 Jan 2018 16:23:15 +0100
Subject: refactor groups controller to match EE
---
app/controllers/groups_controller.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 75270a0889b..bb652832cb1 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -118,10 +118,10 @@ class GroupsController < Groups::ApplicationController
end
def group_params
- params.require(:group).permit(group_params_ce)
+ params.require(:group).permit(group_params_attributes)
end
- def group_params_ce
+ def group_params_attributes
[
:avatar,
:description,
--
cgit v1.2.1
From 2f1a55fa4ef7bbb19ba1bc4de9543878682253d7 Mon Sep 17 00:00:00 2001
From: Clement Ho
Date: Wed, 31 Jan 2018 15:24:54 +0000
Subject: Use axios instead of jquery ajax for setCiStatusFavicon
---
app/assets/javascripts/lib/utils/common_utils.js | 17 ++++-------
spec/javascripts/job_spec.js | 21 ++++---------
spec/javascripts/lib/utils/common_utils_spec.js | 39 ++++++++++++++++--------
3 files changed, 38 insertions(+), 39 deletions(-)
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 03918762842..8018ec411c1 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,4 +1,5 @@
import { getLocationHash } from './url_utility';
+import axios from './axios_utils';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
@@ -382,22 +383,16 @@ export const resetFavicon = () => {
}
};
-export const setCiStatusFavicon = (pageUrl) => {
- $.ajax({
- url: pageUrl,
- dataType: 'json',
- success: (data) => {
+export const setCiStatusFavicon = pageUrl =>
+ axios.get(pageUrl)
+ .then(({ data }) => {
if (data && data.favicon) {
setFavicon(data.favicon);
} else {
resetFavicon();
}
- },
- error: () => {
- resetFavicon();
- },
- });
-};
+ })
+ .catch(resetFavicon);
export const spriteIcon = (icon, className = '') => {
const classAttribute = className.length > 0 ? `class="${className}"` : '';
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index feb341d22e6..0452934ea9e 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -58,8 +58,7 @@ describe('Job', () => {
it('updates the build trace on an interval', function () {
const deferred1 = $.Deferred();
const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl');
deferred1.resolve({
@@ -70,9 +69,7 @@ describe('Job', () => {
complete: false,
});
- deferred2.resolve();
-
- deferred3.resolve({
+ deferred2.resolve({
html: 'More ',
status: 'running',
state: 'finalstate',
@@ -94,9 +91,8 @@ describe('Job', () => {
it('replaces the entire build trace', () => {
const deferred1 = $.Deferred();
const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl');
@@ -107,9 +103,7 @@ describe('Job', () => {
complete: false,
});
- deferred2.resolve();
-
- deferred3.resolve({
+ deferred2.resolve({
html: 'Different ',
status: 'running',
append: false,
@@ -170,9 +164,8 @@ describe('Job', () => {
it('shows incremented size', () => {
const deferred1 = $.Deferred();
const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl');
@@ -184,8 +177,6 @@ describe('Job', () => {
total: 100,
});
- deferred2.resolve();
-
this.job = new Job();
expect(
@@ -194,7 +185,7 @@ describe('Job', () => {
jasmine.clock().tick(4001);
- deferred3.resolve({
+ deferred2.resolve({
html: 'Update ',
status: 'success',
append: true,
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 0a9d815f469..1052b4e7c20 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -1,6 +1,8 @@
/* eslint-disable promise/catch-or-return */
import * as commonUtils from '~/lib/utils/common_utils';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
describe('common_utils', () => {
describe('parseUrl', () => {
@@ -413,37 +415,48 @@ describe('common_utils', () => {
describe('setCiStatusFavicon', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
+ let mock;
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
document.body.appendChild(favicon);
+ mock = new MockAdapter(axios);
});
afterEach(() => {
+ mock.restore();
document.body.removeChild(document.getElementById('favicon'));
});
- it('should reset favicon in case of error', () => {
- const favicon = document.getElementById('favicon');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.error();
- expect(favicon.getAttribute('href')).toEqual('null');
- });
+ it('should reset favicon in case of error', (done) => {
+ mock.onGet(BUILD_URL).networkError();
- commonUtils.setCiStatusFavicon(BUILD_URL);
+ commonUtils.setCiStatusFavicon(BUILD_URL)
+ .then(() => {
+ const favicon = document.getElementById('favicon');
+ expect(favicon.getAttribute('href')).toEqual('null');
+ done();
+ })
+ // Error is already caught in catch() block of setCiStatusFavicon,
+ // It won't throw another error for us to catch
+ .catch(done.fail);
});
- it('should set page favicon to CI status favicon based on provided status', () => {
+ it('should set page favicon to CI status favicon based on provided status', (done) => {
const FAVICON_PATH = '//icon_status_success';
- const favicon = document.getElementById('favicon');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success({ favicon: FAVICON_PATH });
- expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
+ mock.onGet(BUILD_URL).reply(200, {
+ favicon: FAVICON_PATH,
});
- commonUtils.setCiStatusFavicon(BUILD_URL);
+ commonUtils.setCiStatusFavicon(BUILD_URL)
+ .then(() => {
+ const favicon = document.getElementById('favicon');
+ expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
+ done();
+ })
+ .catch(done.fail);
});
});
--
cgit v1.2.1
From a3356bce09d1a5e1222254a8e193b98f90d71e4e Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Wed, 31 Jan 2018 15:48:12 +0000
Subject: fixed eslint :see_no_evil:
---
app/assets/javascripts/lib/utils/common_utils.js | 1 -
spec/javascripts/lib/utils/common_utils_spec.js | 1 -
2 files changed, 2 deletions(-)
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 13e0b0de437..c27c4c6e621 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,6 +1,5 @@
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
-import axios from './axios_utils';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 50dac3def60..864ee4995ea 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -1,7 +1,6 @@
/* eslint-disable promise/catch-or-return */
import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
-import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
describe('common_utils', () => {
--
cgit v1.2.1
From c01f81fa084284719dc305486980b5e6c58d429f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?=
Date: Wed, 31 Jan 2018 17:02:50 +0000
Subject: [ci skip] Fix example commands to refer to the correct versions
---
changelogs/unreleased/fix-install-docs.yml | 5 +++++
doc/install/installation.md | 8 ++++----
lib/system_check/app/git_version_check.rb | 2 +-
3 files changed, 10 insertions(+), 5 deletions(-)
create mode 100644 changelogs/unreleased/fix-install-docs.yml
diff --git a/changelogs/unreleased/fix-install-docs.yml b/changelogs/unreleased/fix-install-docs.yml
new file mode 100644
index 00000000000..c2c0dd1364b
--- /dev/null
+++ b/changelogs/unreleased/fix-install-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Update minimum git version to 2.9.5
+merge_request: 16683
+author:
+type: other
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 18e29271d0f..6eb8890cc4f 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -80,7 +80,7 @@ Make sure you have the right version of Git installed
# Install Git
sudo apt-get install -y git-core
- # Make sure Git is version 2.14.3 or higher
+ # Make sure Git is version 2.9.5 or higher
git --version
Is the system packaged Git too old? Remove it and compile from source.
@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.8.4.tar.gz
- echo '626e319f8a24fc0866167ea5f6bf3e2f38f69d6cb2e59e150f13709ca3ebf301 git-2.8.4.tar.gz' | shasum -a256 -c - && tar -xzf git-2.8.4.tar.gz
- cd git-2.8.4/
+ curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.14.3.tar.gz
+ echo '023ffff6d3ba8a1bea779dfecc0ed0bb4ad68ab8601d14435dd8c08416f78d7f git-2.14.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.14.3.tar.gz
+ cd git-2.14.3/
./configure
make prefix=/usr/local all
diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb
index 6ee8c8874ec..44ec888c197 100644
--- a/lib/system_check/app/git_version_check.rb
+++ b/lib/system_check/app/git_version_check.rb
@@ -5,7 +5,7 @@ module SystemCheck
set_check_pass -> { "yes (#{self.current_version})" }
def self.required_version
- @required_version ||= Gitlab::VersionInfo.new(2, 7, 3)
+ @required_version ||= Gitlab::VersionInfo.new(2, 9, 5)
end
def self.current_version
--
cgit v1.2.1
From 2b6307f6ad9d09156c42befe4babbfea40dad052 Mon Sep 17 00:00:00 2001
From: Takuya Noguchi
Date: Sat, 27 Jan 2018 14:35:53 +0900
Subject: Enable RuboCop Style/RegexpLiteral
---
.rubocop_todo.yml | 4 +++-
app/controllers/help_controller.rb | 2 +-
app/controllers/projects_controller.rb | 2 +-
app/helpers/sidekiq_helper.rb | 6 +++---
app/helpers/submodule_helper.rb | 10 +++++-----
app/helpers/tree_helper.rb | 2 +-
app/models/ci/build.rb | 2 +-
app/models/commit_status.rb | 2 +-
app/models/concerns/taskable.rb | 4 ++--
app/models/environment.rb | 2 +-
app/models/project.rb | 4 ++--
app/models/project_services/asana_service.rb | 2 +-
.../project_services/issue_tracker_service.rb | 4 ++--
app/models/project_services/jira_service.rb | 2 +-
app/models/user.rb | 4 ++--
.../42497-rubocop-style-regexpliteral.yml | 5 +++++
config/initializers/1_settings.rb | 6 +++---
config/initializers/active_record_data_types.rb | 2 +-
config/routes/admin.rb | 2 +-
config/routes/group.rb | 4 ++--
config/routes/project.rb | 10 +++++-----
config/routes/uploads.rb | 10 +++++-----
features/steps/project/source/markdown_render.rb | 6 +++---
lib/api/templates.rb | 8 ++++----
lib/api/v3/projects.rb | 2 +-
lib/api/v3/templates.rb | 8 ++++----
lib/banzai/filter/emoji_filter.rb | 4 ++--
lib/banzai/filter/gollum_tags_filter.rb | 4 ++--
lib/container_registry/registry.rb | 2 +-
lib/extracts_path.rb | 4 ++--
.../populate_untracked_uploads.rb | 2 +-
lib/gitlab/ci/build/artifacts/metadata/entry.rb | 2 +-
.../dependency_linker/composer_json_linker.rb | 2 +-
lib/gitlab/dependency_linker/gemfile_linker.rb | 2 +-
lib/gitlab/dependency_linker/podspec_linker.rb | 2 +-
lib/gitlab/email/reply_parser.rb | 2 +-
lib/gitlab/file_detector.rb | 22 +++++++++++-----------
lib/gitlab/git.rb | 2 +-
lib/gitlab/git/blob.rb | 4 ++--
lib/gitlab/git/path_helper.rb | 2 +-
lib/gitlab/git/ref.rb | 2 +-
lib/gitlab/git/repository.rb | 4 ++--
lib/gitlab/git/repository_mirroring.rb | 2 +-
.../github_import/representation/diff_note.rb | 2 +-
lib/gitlab/github_import/representation/note.rb | 2 +-
lib/gitlab/import_export/file_importer.rb | 2 +-
lib/gitlab/metrics/subscribers/action_view.rb | 2 +-
lib/gitlab/middleware/go.rb | 4 ++--
lib/gitlab/middleware/static.rb | 2 +-
.../quick_actions/spend_time_and_date_separator.rb | 2 +-
lib/gitlab/repo_path.rb | 2 +-
lib/gitlab/setup_helper.rb | 2 +-
lib/gitlab/sherlock/file_sample.rb | 2 +-
lib/gitlab/sherlock/middleware.rb | 2 +-
lib/gitlab/sherlock/query.rb | 4 ++--
lib/gitlab/upgrader.rb | 2 +-
qa/qa/page/menu/side.rb | 2 +-
qa/spec/runtime/rsa_key.rb | 2 +-
rubocop/cop/migration/update_column_in_batches.rb | 2 +-
spec/controllers/projects/jobs_controller_spec.rb | 4 ++--
spec/controllers/projects/todos_controller_spec.rb | 4 ++--
spec/features/atom/users_spec.rb | 4 ++--
.../merge_request/user_resolves_conflicts_spec.rb | 10 +++++-----
spec/helpers/groups_helper_spec.rb | 4 ++--
spec/helpers/labels_helper_spec.rb | 2 +-
spec/helpers/version_check_helper_spec.rb | 2 +-
.../filter/commit_range_reference_filter_spec.rb | 4 ++--
.../banzai/filter/commit_reference_filter_spec.rb | 6 +++---
.../filter/external_issue_reference_filter_spec.rb | 2 +-
spec/lib/banzai/filter/image_link_filter_spec.rb | 4 ++--
.../banzai/filter/issue_reference_filter_spec.rb | 6 +++---
.../filter/merge_request_reference_filter_spec.rb | 4 ++--
.../banzai/filter/snippet_reference_filter_spec.rb | 6 +++---
.../banzai/filter/user_reference_filter_spec.rb | 4 ++--
spec/lib/gitlab/git/repository_spec.rb | 2 +-
spec/lib/gitlab/path_regex_spec.rb | 2 +-
spec/mailers/notify_spec.rb | 2 +-
spec/models/project_services/jira_service_spec.rb | 4 ++--
.../merge_when_pipeline_succeeds_service_spec.rb | 2 +-
spec/services/system_note_service_spec.rb | 4 ++--
spec/tasks/gitlab/backup_rake_spec.rb | 2 +-
spec/tasks/gitlab/git_rake_spec.rb | 2 +-
spec/uploaders/file_uploader_spec.rb | 2 +-
spec/uploaders/job_artifact_uploader_spec.rb | 2 +-
84 files changed, 156 insertions(+), 149 deletions(-)
create mode 100644 changelogs/unreleased/42497-rubocop-style-regexpliteral.yml
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 442d61bcf4f..7a12c8473f3 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -704,7 +704,9 @@ Style/RedundantSelf:
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
- Enabled: false
+ Enabled: true
+ EnforcedStyle: mixed
+ AllowInnerSlashes: false
# Offense count: 36
# Cop supports --auto-correct.
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 38f379dbf4f..a394521698c 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -5,7 +5,7 @@ class HelpController < ApplicationController
# Taken from Jekyll
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13
- YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
+ YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
def index
# Remove YAML frontmatter so that it doesn't look weird
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index e6e2b219e6a..8158934322d 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -403,6 +403,6 @@ class ProjectsController < Projects::ApplicationController
# to
# localhost/group/project
#
- redirect_to request.original_url.sub(/\.git\/?\Z/, '') if params[:format] == 'git'
+ redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git'
end
end
diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb
index 55f4da0ef85..50aeb7f4b82 100644
--- a/app/helpers/sidekiq_helper.rb
+++ b/app/helpers/sidekiq_helper.rb
@@ -1,12 +1,12 @@
module SidekiqHelper
- SIDEKIQ_PS_REGEXP = /\A
+ SIDEKIQ_PS_REGEXP = %r{\A
(?\d+)\s+
(?[\d\.,]+)\s+
(?[\d\.,]+)\s+
- (?[DIEKNRSTVWXZNLpsl\+<>\/\d]+)\s+
+ (?[DIEKNRSTVWXZNLpsl\+<>/\d]+)\s+
(?.+?)\s+
(?(?:ruby\d+:\s+)?sidekiq.*\].*)
- \z/x
+ \z}x
def parse_sidekiq_ps(line)
match = line.strip.match(SIDEKIQ_PS_REGEXP)
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 1db9ae3839c..9151543dfdc 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -11,7 +11,7 @@ module SubmoduleHelper
url = File.join(Gitlab.config.gitlab.url, @project.full_path)
end
- if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
+ if url =~ %r{([^/:]+)/([^/]+(?:\.git)?)\Z}
namespace, project = $1, $2
gitlab_hosts = [Gitlab.config.gitlab.url,
Gitlab.config.gitlab_shell.ssh_path_prefix]
@@ -23,7 +23,7 @@ module SubmoduleHelper
end
end
- namespace.sub!(/\A\//, '')
+ namespace.sub!(%r{\A/}, '')
project.rstrip!
project.sub!(/\.git\z/, '')
@@ -47,11 +47,11 @@ module SubmoduleHelper
protected
def github_dot_com_url?(url)
- url =~ /github\.com[\/:][^\/]+\/[^\/]+\Z/
+ url =~ %r{github\.com[/:][^/]+/[^/]+\Z}
end
def gitlab_dot_com_url?(url)
- url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/
+ url =~ %r{gitlab\.com[/:][^/]+/[^/]+\Z}
end
def self_url?(url, namespace, project)
@@ -65,7 +65,7 @@ module SubmoduleHelper
def relative_self_url?(url)
# (./)?(../repo.git) || (./)?(../../project/repo.git) )
- url =~ /\A((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*(\.git)?\z/ || url =~ /\A((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*(\.git)?\z/
+ url =~ %r{\A((\./)?(\.\./))(?!(\.\.)|(.*/)).*(\.git)?\z} || url =~ %r{\A((\./)?(\.\./){2})(?!(\.\.))([^/]*)/(?!(\.\.)|(.*/)).*(\.git)?\z}
end
def standard_links(host, namespace, project, commit)
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 5b2ea38a03d..d39cac0f510 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -110,7 +110,7 @@ module TreeHelper
# returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(root_path, tree)
- return tree.flat_path.sub(/\A#{root_path}\//, '') if tree.flat_path.present?
+ return tree.flat_path.sub(%r{\A#{root_path}/}, '') if tree.flat_path.present?
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir?
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6ced5fb0e24..78906e7a968 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -292,7 +292,7 @@ module Ci
def repo_url
auth = "gitlab-ci-token:#{ensure_token!}@"
- project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
+ project.http_url_to_repo.sub(%r{^https?://}) do |prefix|
prefix + auth
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index c0263c0b4e2..3469d5d795c 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base
end
def group_name
- name.to_s.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
+ name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip
end
def failed_but_allowed?
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index d07041c2fdf..ccd6f0e0a7d 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -9,13 +9,13 @@ require 'task_list/filter'
module Taskable
COMPLETED = 'completed'.freeze
INCOMPLETE = 'incomplete'.freeze
- ITEM_PATTERN = /
+ ITEM_PATTERN = %r{
^
\s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
\s+ # whitespace prefix has to be always presented for a list item
(\[\s\]|\[[xX]\]) # checkbox
(\s.+) # followed by whitespace and some text.
- /x
+ }x
def self.get_tasks(content)
content.to_s.scan(ITEM_PATTERN).map do |checkbox, label|
diff --git a/app/models/environment.rb b/app/models/environment.rb
index bf69b4c50f0..2f6eae605ee 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -115,7 +115,7 @@ class Environment < ActiveRecord::Base
def formatted_external_url
return nil unless external_url
- external_url.gsub(/\A.*?:\/\//, '')
+ external_url.gsub(%r{\A.*?://}, '')
end
def stop_action?
diff --git a/app/models/project.rb b/app/models/project.rb
index d0d0fd6e093..4def590a7a9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -234,7 +234,7 @@ class Project < ActiveRecord::Base
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :ci_config_path,
- format: { without: /(\.{2}|\A\/)/,
+ format: { without: %r{(\.{2}|\A/)},
message: 'cannot include leading slash or directory traversal.' },
length: { maximum: 255 },
allow_blank: true
@@ -1338,7 +1338,7 @@ class Project < ActiveRecord::Base
host = "#{subdomain}.#{Settings.pages.host}".downcase
# The host in URL always needs to be downcased
- url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix|
+ url = Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix|
"#{prefix}#{subdomain}."
end.downcase
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 9ce2d1153a7..109258d1eb7 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -84,7 +84,7 @@ http://app.asana.com/-/account_api'
# - fix/ed/es/ing
# - close/s/d
# - closing
- issue_finder = /(fix\w*|clos[ei]\w*+)?\W*(?:https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)|#(\d+))/i
+ issue_finder = %r{(fix\w*|clos[ei]\w*+)?\W*(?:https://app\.asana\.com/\d+/\d+/(\d+)|#(\d+))}i
message.scan(issue_finder).each do |tuple|
# tuple will be
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 31984c5d7ed..5fb15c383ca 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -10,9 +10,9 @@ class IssueTrackerService < Service
# overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
def self.reference_pattern(only_long: false)
if only_long
- %r{(\b[A-Z][A-Z0-9_]+-)(?\d+)}
+ /(\b[A-Z][A-Z0-9_]+-)(?\d+)/
else
- %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?\d+)}
+ /(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?\d+)/
end
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 23147d7f666..30eafe31454 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -19,7 +19,7 @@ class JiraService < IssueTrackerService
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def self.reference_pattern(only_long: true)
- @reference_pattern ||= %r{(?\b([A-Z][A-Z0-9_]+-)\d+)}
+ @reference_pattern ||= /(?\b([A-Z][A-Z0-9_]+-)\d+)/
end
def initialize_properties
diff --git a/app/models/user.rb b/app/models/user.rb
index 9403da98268..fb5d56a68b0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -842,13 +842,13 @@ class User < ActiveRecord::Base
end
def full_website_url
- return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//
+ return "http://#{website_url}" if website_url !~ %r{\Ahttps?://}
website_url
end
def short_website_url
- website_url.sub(/\Ahttps?:\/\//, '')
+ website_url.sub(%r{\Ahttps?://}, '')
end
def all_ssh_keys
diff --git a/changelogs/unreleased/42497-rubocop-style-regexpliteral.yml b/changelogs/unreleased/42497-rubocop-style-regexpliteral.yml
new file mode 100644
index 00000000000..6053883bac4
--- /dev/null
+++ b/changelogs/unreleased/42497-rubocop-style-regexpliteral.yml
@@ -0,0 +1,5 @@
+---
+title: Enable RuboCop Style/RegexpLiteral
+merge_request: 16752
+author: Takuya Noguchi
+type: other
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 899e612ffbd..5b4e6b5db88 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -110,7 +110,7 @@ class Settings < Settingslogic
url = "http://#{url}" unless url.start_with?('http')
# Get rid of the path so that we don't even have to encode it
- url_without_path = url.sub(%r{(https?://[^\/]+)/?.*}, '\1')
+ url_without_path = url.sub(%r{(https?://[^/]+)/?.*}, '\1')
URI.parse(url_without_path).host
end
@@ -469,10 +469,10 @@ end
# repository_downloads_path value.
#
repositories_storages = Settings.repositories.storages.values
-repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '')
+repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(%r{/$}, '')
repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
-if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(/\/$/, '')) }
+if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(%r{/$}, '')) }
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')
end
diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb
index 0359e14b232..fda13d0c4cb 100644
--- a/config/initializers/active_record_data_types.rb
+++ b/config/initializers/active_record_data_types.rb
@@ -54,7 +54,7 @@ elsif Gitlab::Database.mysql?
def initialize_type_map(mapping)
super mapping
- mapping.register_type(%r(timestamp)i) do |sql_type|
+ mapping.register_type(/timestamp/i) do |sql_type|
precision = extract_precision(sql_type)
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlDateTimeWithTimeZone.new(precision: precision)
end
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 57e401c9b89..3cca1210e39 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -1,5 +1,5 @@
namespace :admin do
- resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
+ resources :users, constraints: { id: %r{[a-zA-Z./0-9_\-]+} } do
resources :keys, only: [:show, :destroy]
resources :identities, except: [:show]
resources :impersonation_tokens, only: [:index, :create] do
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 976837a246d..24c76bc55ab 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -35,7 +35,7 @@ constraints(GroupUrlConstrainer.new) do
post :toggle_subscription, on: :member
end
- resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :edit, :update, :new, :create] do
+ resources :milestones, constraints: { id: %r{[^/]+} }, only: [:index, :show, :edit, :update, :new, :create] do
member do
get :merge_requests
get :participants
@@ -52,7 +52,7 @@ constraints(GroupUrlConstrainer.new) do
resources :uploads, only: [:create] do
collection do
- get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ }
+ get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
end
end
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 0496bd85b4e..bcaa68c8ce5 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -40,7 +40,7 @@ constraints(ProjectUrlConstrainer.new) do
#
# Templates
#
- get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: /[^\/]+/ }
+ get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} }
resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
@@ -55,7 +55,7 @@ constraints(ProjectUrlConstrainer.new) do
end
resource :pages, only: [:show, :destroy] do
- resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ }
+ resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: %r{[^/]+} }
end
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
@@ -65,7 +65,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
- resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
+ resources :services, constraints: { id: %r{[^/]+} }, only: [:index, :edit, :update] do
member do
put :test
end
@@ -346,7 +346,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
- resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
+ resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+]+} }, concerns: :access_requestable do
collection do
delete :leave
@@ -379,7 +379,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :uploads, only: [:create] do
collection do
- get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ }
+ get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
end
end
diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb
index d7bca8310e4..6370645bcb9 100644
--- a/config/routes/uploads.rb
+++ b/config/routes/uploads.rb
@@ -2,17 +2,17 @@ scope path: :uploads do
# Note attachments and User/Group/Project avatars
get "-/system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
- constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
+ constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: %r{[^/]+} }
# show uploads for models, snippets (notes) available for now
get '-/system/:model/:id/:secret/:filename',
to: 'uploads#show',
- constraints: { model: /personal_snippet/, id: /\d+/, filename: /[^\/]+/ }
+ constraints: { model: /personal_snippet/, id: /\d+/, filename: %r{[^/]+} }
# show temporary uploads
get '-/system/temp/:secret/:filename',
to: 'uploads#show',
- constraints: { filename: /[^\/]+/ }
+ constraints: { filename: %r{[^/]+} }
# Appearance
get "-/system/:model/:mounted_as/:id/:filename",
@@ -22,7 +22,7 @@ scope path: :uploads do
# Project markdown uploads
get ":namespace_id/:project_id/:secret/:filename",
to: "projects/uploads#show",
- constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ }
+ constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: %r{[^/]+} }
# create uploads for models, snippets (notes) available for now
post ':model',
@@ -34,4 +34,4 @@ end
# Redirect old note attachments path to new uploads path.
get "files/note/:id/:filename",
to: redirect("uploads/note/attachment/%{id}/%{filename}"),
- constraints: { filename: /[^\/]+/ }
+ constraints: { filename: %r{[^/]+} }
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index fc4ef26f6b6..db99c179439 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -193,7 +193,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'The link with text "/ID" should have url "tree/markdownID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
+ find('a', text: %r{^/#id$})['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
end
step 'The link with text "README.mdID" '\
@@ -203,7 +203,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'The link with text "d/README.mdID" should have '\
'url "blob/markdown/d/README.mdID"' do
- find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
+ find('a', text: %r{^d/README.md#id$})['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
end
step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
@@ -212,7 +212,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ find('a', text: %r{^/#id$})['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
# Wiki
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 6550b331fb8..41862768a3f 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -17,15 +17,15 @@ module API
}
}.freeze
PROJECT_TEMPLATE_REGEX =
- /[\<\{\[]
+ %r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
- [\>\}\]]/xi.freeze
+ [\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
- /[\<\{\[]
+ %r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
- [\>\}\]]/xi.freeze
+ [\>\}\]]}xi.freeze
helpers do
def parsed_license_template
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index a7f0813bf74..c856ba99f09 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -173,7 +173,7 @@ module API
use :sort_params
use :pagination
end
- get "/search/:query", requirements: { query: /[^\/]+/ } do
+ get "/search/:query", requirements: { query: %r{[^/]+} } do
search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
projects = search_service.objects('projects', params[:page], false)
projects = projects.reorder(params[:order_by] => params[:sort])
diff --git a/lib/api/v3/templates.rb b/lib/api/v3/templates.rb
index 7298203df10..b82b02b5f49 100644
--- a/lib/api/v3/templates.rb
+++ b/lib/api/v3/templates.rb
@@ -16,15 +16,15 @@ module API
}
}.freeze
PROJECT_TEMPLATE_REGEX =
- /[\<\{\[]
+ %r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
- [\>\}\]]/xi.freeze
+ [\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
FULLNAME_TEMPLATE_REGEX =
- /[\<\{\[]
+ %r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
- [\>\}\]]/xi.freeze
+ [\>\}\]]}xi.freeze
DEPRECATION_MESSAGE = ' This endpoint is deprecated and has been removed in V4.'.freeze
helpers do
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index 6255a611dbe..b82c6ca6393 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -54,9 +54,9 @@ module Banzai
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
@emoji_pattern ||=
- /(?<=[^[:alnum:]:]|\n|^)
+ %r{(?<=[^[:alnum:]:]|\n|^)
:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):
- (?=[^[:alnum:]:]|$)/x
+ (?=[^[:alnum:]:]|$)}x
end
# Build a regexp that matches all valid unicode emojis names.
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 2e259904673..c2b42673376 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -51,10 +51,10 @@ module Banzai
# See https://github.com/gollum/gollum/wiki
#
# Rubular: http://rubular.com/r/7dQnE5CUCH
- TAGS_PATTERN = %r{\[\[(.+?)\]\]}.freeze
+ TAGS_PATTERN = /\[\[(.+?)\]\]/.freeze
# Pattern to match allowed image extensions
- ALLOWED_IMAGE_EXTENSIONS = %r{.+(jpg|png|gif|svg|bmp)\z}i.freeze
+ ALLOWED_IMAGE_EXTENSIONS = /.+(jpg|png|gif|svg|bmp)\z/i.freeze
def call
search_text_nodes(doc).each do |node|
diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb
index 63bce655f57..f90d711474a 100644
--- a/lib/container_registry/registry.rb
+++ b/lib/container_registry/registry.rb
@@ -11,7 +11,7 @@ module ContainerRegistry
private
def default_path
- @uri.sub(/^https?:\/\//, '')
+ @uri.sub(%r{^https?://}, '')
end
end
end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index d8aca3304c5..a9b04c183ad 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -56,7 +56,7 @@ module ExtractsPath
if valid_refs.length == 0
# No exact ref match, so just try our best
- pair = id.match(/([^\/]+)(.*)/).captures
+ pair = id.match(%r{([^/]+)(.*)}).captures
else
# There is a distinct possibility that multiple refs prefix the ID.
# Use the longest match to maximize the chance that we have the
@@ -68,7 +68,7 @@ module ExtractsPath
end
# Remove ending slashes from path
- pair[1].gsub!(/^\/|\/$/, '')
+ pair[1].gsub!(%r{^/|/$}, '')
pair
end
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb
index 81e95e5832d..d60e41d9f9d 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb
@@ -12,7 +12,7 @@ module Gitlab
# Ends with /:random_hex/:filename
FILE_UPLOADER_PATH = %r{/\h+/[^/]+\z}
- FULL_PATH_CAPTURE = %r{\A(.+)#{FILE_UPLOADER_PATH}}
+ FULL_PATH_CAPTURE = /\A(.+)#{FILE_UPLOADER_PATH}/
# These regex patterns are tested against a relative path, relative to
# the upload directory.
diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
index 5b2f09e03ea..428c0505808 100644
--- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
@@ -97,7 +97,7 @@ module Gitlab
end
def total_size
- descendant_pattern = %r{^#{Regexp.escape(@path.to_s)}}
+ descendant_pattern = /^#{Regexp.escape(@path.to_s)}/
entries.sum do |path, entry|
(entry[:size] if path =~ descendant_pattern).to_i
end
diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb
index 0245bf4077a..cfd4ec15125 100644
--- a/lib/gitlab/dependency_linker/composer_json_linker.rb
+++ b/lib/gitlab/dependency_linker/composer_json_linker.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def package_url(name)
- "https://packagist.org/packages/#{name}" if name =~ %r{\A#{REPO_REGEX}\z}
+ "https://packagist.org/packages/#{name}" if name =~ /\A#{REPO_REGEX}\z/
end
end
end
diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb
index d034ea67387..bfea836bcb2 100644
--- a/lib/gitlab/dependency_linker/gemfile_linker.rb
+++ b/lib/gitlab/dependency_linker/gemfile_linker.rb
@@ -15,7 +15,7 @@ module Gitlab
link_regex(/(github:|:github\s*=>)\s*['"](?[^'"]+)['"]/, &method(:github_url))
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
- link_regex(%r{(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]}, &:itself)
+ link_regex(/(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]/, &:itself)
# Link `source "https://rubygems.org"` to https://rubygems.org
link_method_call('source', URL_REGEX, &:itself)
diff --git a/lib/gitlab/dependency_linker/podspec_linker.rb b/lib/gitlab/dependency_linker/podspec_linker.rb
index a52c7a02439..924e55e9820 100644
--- a/lib/gitlab/dependency_linker/podspec_linker.rb
+++ b/lib/gitlab/dependency_linker/podspec_linker.rb
@@ -12,7 +12,7 @@ module Gitlab
def link_dependencies
link_method_call('homepage', URL_REGEX, &:itself)
- link_regex(%r{(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]}, &:itself)
+ link_regex(/(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]/, &:itself)
link_method_call('license', &method(:license_url))
link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))
diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb
index 558df87f36d..01c28d051ee 100644
--- a/lib/gitlab/email/reply_parser.rb
+++ b/lib/gitlab/email/reply_parser.rb
@@ -43,7 +43,7 @@ module Gitlab
return "" unless decoded
# Certain trigger phrases that means we didn't parse correctly
- if decoded =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/
+ if decoded =~ %r{(Content\-Type\:|multipart/alternative|text/plain)}
return ""
end
diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb
index 0e9ef4f897c..cc2638172ec 100644
--- a/lib/gitlab/file_detector.rb
+++ b/lib/gitlab/file_detector.rb
@@ -6,14 +6,14 @@ module Gitlab
module FileDetector
PATTERNS = {
# Project files
- readme: /\Areadme[^\/]*\z/i,
- changelog: /\A(changelog|history|changes|news)[^\/]*\z/i,
- license: /\A(licen[sc]e|copying)(\.[^\/]+)?\z/i,
- contributing: /\Acontributing[^\/]*\z/i,
+ readme: %r{\Areadme[^/]*\z}i,
+ changelog: %r{\A(changelog|history|changes|news)[^/]*\z}i,
+ license: %r{\A(licen[sc]e|copying)(\.[^/]+)?\z}i,
+ contributing: %r{\Acontributing[^/]*\z}i,
version: 'version',
avatar: /\Alogo\.(png|jpg|gif)\z/,
- issue_template: /\A\.gitlab\/issue_templates\/[^\/]+\.md\z/,
- merge_request_template: /\A\.gitlab\/merge_request_templates\/[^\/]+\.md\z/,
+ issue_template: %r{\A\.gitlab/issue_templates/[^/]+\.md\z},
+ merge_request_template: %r{\A\.gitlab/merge_request_templates/[^/]+\.md\z},
# Configuration files
gitignore: '.gitignore',
@@ -22,17 +22,17 @@ module Gitlab
route_map: '.gitlab/route-map.yml',
# Dependency files
- cartfile: /\ACartfile[^\/]*\z/,
+ cartfile: %r{\ACartfile[^/]*\z},
composer_json: 'composer.json',
gemfile: /\A(Gemfile|gems\.rb)\z/,
gemfile_lock: 'Gemfile.lock',
- gemspec: /\A[^\/]*\.gemspec\z/,
+ gemspec: %r{\A[^/]*\.gemspec\z},
godeps_json: 'Godeps.json',
package_json: 'package.json',
podfile: 'Podfile',
- podspec_json: /\A[^\/]*\.podspec\.json\z/,
- podspec: /\A[^\/]*\.podspec\z/,
- requirements_txt: /\A[^\/]*requirements\.txt\z/,
+ podspec_json: %r{\A[^/]*\.podspec\.json\z},
+ podspec: %r{\A[^/]*\.podspec\z},
+ requirements_txt: %r{\A[^/]*requirements\.txt\z},
yarn_lock: 'yarn.lock'
}.freeze
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 71647099f83..c77db0f685f 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -11,7 +11,7 @@ module Gitlab
include Gitlab::EncodingHelper
def ref_name(ref)
- encode!(ref).sub(/\Arefs\/(tags|heads|remotes)\//, '')
+ encode!(ref).sub(%r{\Arefs/(tags|heads|remotes)/}, '')
end
def branch_name(ref)
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 13120120223..4828301dbb9 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -107,7 +107,7 @@ module Gitlab
def find_entry_by_path(repository, root_id, path)
root_tree = repository.lookup(root_id)
# Strip leading slashes
- path[/^\/*/] = ''
+ path[%r{^/*}] = ''
path_arr = path.split('/')
entry = root_tree.find do |entry|
@@ -140,7 +140,7 @@ module Gitlab
def find_by_gitaly(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
return unless path
- path = path.sub(/\A\/*/, '')
+ path = path.sub(%r{\A/*}, '')
path = '/' if path.empty?
name = File.basename(path)
diff --git a/lib/gitlab/git/path_helper.rb b/lib/gitlab/git/path_helper.rb
index 42c80aabd0a..155cf52f050 100644
--- a/lib/gitlab/git/path_helper.rb
+++ b/lib/gitlab/git/path_helper.rb
@@ -6,7 +6,7 @@ module Gitlab
class << self
def normalize_path(filename)
# Strip all leading slashes so that //foo -> foo
- filename[/^\/*/] = ''
+ filename[%r{^/*}] = ''
# Expand relative paths (e.g. foo/../bar)
filename = Pathname.new(filename)
diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb
index a3ba9475ad0..fa71a4e7ea7 100644
--- a/lib/gitlab/git/ref.rb
+++ b/lib/gitlab/git/ref.rb
@@ -23,7 +23,7 @@ module Gitlab
# Ex.
# Ref.extract_branch_name('refs/heads/master') #=> 'master'
def self.extract_branch_name(str)
- str.gsub(/\Arefs\/heads\//, '')
+ str.gsub(%r{\Arefs/heads/}, '')
end
# Gitaly: this method will probably be migrated indirectly via its call sites.
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 8137c582c0f..6d3b918aaae 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1397,7 +1397,7 @@ module Gitlab
end
def search_files_by_name(query, ref)
- safe_query = Regexp.escape(query.sub(/^\/*/, ""))
+ safe_query = Regexp.escape(query.sub(%r{^/*}, ""))
return [] if empty? || safe_query.blank?
@@ -2025,7 +2025,7 @@ module Gitlab
target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
rescue Rugged::ReferenceError => e
- raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/
+ raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ %r{'refs/heads/#{ref}'}
raise InvalidRef.new("Invalid reference #{start_point}")
end
diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb
index effb1f0ca19..dc424a433fb 100644
--- a/lib/gitlab/git/repository_mirroring.rb
+++ b/lib/gitlab/git/repository_mirroring.rb
@@ -43,7 +43,7 @@ module Gitlab
branches = []
rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref|
- name = ref.name.sub(/\Arefs\/remotes\/#{remote_name}\//, '')
+ name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '')
begin
target_commit = Gitlab::Git::Commit.find(self, ref.target)
diff --git a/lib/gitlab/github_import/representation/diff_note.rb b/lib/gitlab/github_import/representation/diff_note.rb
index bb7439a0641..be1334ca98a 100644
--- a/lib/gitlab/github_import/representation/diff_note.rb
+++ b/lib/gitlab/github_import/representation/diff_note.rb
@@ -13,7 +13,7 @@ module Gitlab
:diff_hunk, :author, :note, :created_at, :updated_at,
:github_id
- NOTEABLE_ID_REGEX = /\/pull\/(?\d+)/i
+ NOTEABLE_ID_REGEX = %r{/pull/(?\d+)}i
# Builds a diff note from a GitHub API response.
#
diff --git a/lib/gitlab/github_import/representation/note.rb b/lib/gitlab/github_import/representation/note.rb
index a68bc4c002f..070e3b2db8d 100644
--- a/lib/gitlab/github_import/representation/note.rb
+++ b/lib/gitlab/github_import/representation/note.rb
@@ -12,7 +12,7 @@ module Gitlab
expose_attribute :noteable_id, :noteable_type, :author, :note,
:created_at, :updated_at, :github_id
- NOTEABLE_TYPE_REGEX = /\/(?(pull|issues))\/(?\d+)/i
+ NOTEABLE_TYPE_REGEX = %r{/(?(pull|issues))/(?\d+)}i
# Builds a note from a GitHub API response.
#
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 5c971564a73..0f4c3498036 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -59,7 +59,7 @@ module Gitlab
end
def extracted_files
- Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ /.*\/\.{1,2}$/ }
+ Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ %r{.*/\.{1,2}$} }
end
end
end
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index 274436ca2b4..b600e8a2a50 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -34,7 +34,7 @@ module Gitlab
end
def relative_path(path)
- path.gsub(/^#{Rails.root.to_s}\/?/, '')
+ path.gsub(%r{^#{Rails.root.to_s}/?}, '')
end
def values_for(event)
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index c6a56277922..afbc2600634 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -56,12 +56,12 @@ module Gitlab
end
def strip_url(url)
- url.gsub(/\Ahttps?:\/\//, '')
+ url.gsub(%r{\Ahttps?://}, '')
end
def project_path(request)
path_info = request.env["PATH_INFO"]
- path_info.sub!(/^\//, '')
+ path_info.sub!(%r{^/}, '')
project_path_match = "#{path_info}/".match(PROJECT_PATH_REGEX)
return unless project_path_match
diff --git a/lib/gitlab/middleware/static.rb b/lib/gitlab/middleware/static.rb
index 85ffa8aca68..aa1e9dc0fdb 100644
--- a/lib/gitlab/middleware/static.rb
+++ b/lib/gitlab/middleware/static.rb
@@ -1,7 +1,7 @@
module Gitlab
module Middleware
class Static < ActionDispatch::Static
- UPLOADS_REGEX = /\A\/uploads(\/|\z)/.freeze
+ UPLOADS_REGEX = %r{\A/uploads(/|\z)}.freeze
def call(env)
return @app.call(env) if env['PATH_INFO'] =~ UPLOADS_REGEX
diff --git a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb
index 3f52402b31f..7328c517a30 100644
--- a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb
+++ b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb
@@ -9,7 +9,7 @@ module Gitlab
# if date doesn't present return time with current date
# in other cases return nil
class SpendTimeAndDateSeparator
- DATE_REGEX = /(\d{2,4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})/
+ DATE_REGEX = %r{(\d{2,4}[/\-.]\d{1,2}[/\-.]\d{1,2})}
def initialize(spend_command_arg)
@spend_arg = spend_command_arg
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 3591fa9145e..79265cf952d 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -30,7 +30,7 @@ module Gitlab
raise NotFoundError.new("No known storage path matches #{repo_path.inspect}")
end
- result.sub(/\A\/*/, '')
+ result.sub(%r{\A/*}, '')
end
def self.find_project(project_path)
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index d01213bb6e0..e90a90508a2 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -31,7 +31,7 @@ module Gitlab
storages << { name: 'test_second_storage', path: Rails.root.join('tmp', 'tests', 'second_storage').to_s }
end
- config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages }
+ config = { socket_path: address.sub(/\Aunix:/, ''), storage: storages }
config[:auth] = { token: 'secret' } if Rails.env.test?
config[:'gitaly-ruby'] = { dir: File.join(gitaly_dir, 'ruby') } if gitaly_ruby
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
diff --git a/lib/gitlab/sherlock/file_sample.rb b/lib/gitlab/sherlock/file_sample.rb
index 8a3e1a5e5bf..89072b01f2e 100644
--- a/lib/gitlab/sherlock/file_sample.rb
+++ b/lib/gitlab/sherlock/file_sample.rb
@@ -16,7 +16,7 @@ module Gitlab
end
def relative_path
- @relative_path ||= @file.gsub(/^#{Rails.root.to_s}\/?/, '')
+ @relative_path ||= @file.gsub(%r{^#{Rails.root.to_s}/?}, '')
end
def to_param
diff --git a/lib/gitlab/sherlock/middleware.rb b/lib/gitlab/sherlock/middleware.rb
index 687332fc5fc..4c88e33699a 100644
--- a/lib/gitlab/sherlock/middleware.rb
+++ b/lib/gitlab/sherlock/middleware.rb
@@ -2,7 +2,7 @@ module Gitlab
module Sherlock
# Rack middleware used for tracking request metrics.
class Middleware
- CONTENT_TYPES = /text\/html|application\/json/i
+ CONTENT_TYPES = %r{text/html|application/json}i
IGNORE_PATHS = %r{^/sherlock}
diff --git a/lib/gitlab/sherlock/query.rb b/lib/gitlab/sherlock/query.rb
index 948bf5e6528..02ddc3f47eb 100644
--- a/lib/gitlab/sherlock/query.rb
+++ b/lib/gitlab/sherlock/query.rb
@@ -4,7 +4,7 @@ module Gitlab
attr_reader :id, :query, :started_at, :finished_at, :backtrace
# SQL identifiers that should be prefixed with newlines.
- PREFIX_NEWLINE = /
+ PREFIX_NEWLINE = %r{
\s+(FROM
|(LEFT|RIGHT)?INNER\s+JOIN
|(LEFT|RIGHT)?OUTER\s+JOIN
@@ -13,7 +13,7 @@ module Gitlab
|GROUP\s+BY
|ORDER\s+BY
|LIMIT
- |OFFSET)\s+/ix # Vim indent breaks when this is on a newline :<
+ |OFFSET)\s+}ix # Vim indent breaks when this is on a newline :<
# Creates a new Query using a String and a separate Array of bindings.
#
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index d545f2f95f1..024be6aca44 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -49,7 +49,7 @@ module Gitlab
def fetch_git_tags
remote_tags, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git))
- remote_tags.split("\n").grep(/tags\/v#{current_version.major}/)
+ remote_tags.split("\n").grep(%r{tags/v#{current_version.major}})
end
def update_commands
diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb
index 5fdcea20029..7e028add2ef 100644
--- a/qa/qa/page/menu/side.rb
+++ b/qa/qa/page/menu/side.rb
@@ -7,7 +7,7 @@ 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, /link_to.*shortcuts-issues/
element :issues_link_text, "Issues"
element :top_level_items, '.sidebar-top-level-items'
element :activity_link, "title: 'Activity'"
diff --git a/qa/spec/runtime/rsa_key.rb b/qa/spec/runtime/rsa_key.rb
index ff277b9077b..6d7ab4dcd2e 100644
--- a/qa/spec/runtime/rsa_key.rb
+++ b/qa/spec/runtime/rsa_key.rb
@@ -3,7 +3,7 @@ describe QA::Runtime::RSAKey do
subject { described_class.new.public_key }
it 'generates a public RSA key' do
- expect(subject).to match(/\Assh\-rsa AAAA[0-9A-Za-z+\/]+={0,3}\z/)
+ expect(subject).to match(%r{\Assh\-rsa AAAA[0-9A-Za-z+/]+={0,3}\z})
end
end
end
diff --git a/rubocop/cop/migration/update_column_in_batches.rb b/rubocop/cop/migration/update_column_in_batches.rb
index db2b5564297..5461abc5ee0 100644
--- a/rubocop/cop/migration/update_column_in_batches.rb
+++ b/rubocop/cop/migration/update_column_in_batches.rb
@@ -29,7 +29,7 @@ module RuboCop
path = Pathname.new(source_name).relative_path_from(rails_root)
dirname = File.dirname(path)
.sub(%r{\Adb/(migrate|post_migrate)}, 'spec/migrations')
- filename = File.basename(source_name, '.rb').sub(%r{\A\d+_}, '')
+ filename = File.basename(source_name, '.rb').sub(/\A\d+_/, '')
File.join(dirname, "#{filename}_spec.rb")
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index e6a4e7c8257..db595430979 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -137,8 +137,8 @@ describe Projects::JobsController do
it 'exposes needed information' do
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['raw_path']).to match(/jobs\/\d+\/raw\z/)
- expect(json_response.dig('merge_request', 'path')).to match(/merge_requests\/\d+\z/)
+ expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
+ expect(json_response.dig('merge_request', 'path')).to match(%r{merge_requests/\d+\z})
expect(json_response['new_issue_path'])
.to include('/issues/new')
end
diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb
index e2524be7724..1ce7e84bef9 100644
--- a/spec/controllers/projects/todos_controller_spec.rb
+++ b/spec/controllers/projects/todos_controller_spec.rb
@@ -36,7 +36,7 @@ describe Projects::TodosController do
expect(response).to have_gitlab_http_status(200)
expect(json_response['count']).to eq 1
- expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
+ expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}})
end
end
@@ -104,7 +104,7 @@ describe Projects::TodosController do
expect(response).to have_gitlab_http_status(200)
expect(json_response['count']).to eq 1
- expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
+ expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}})
end
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 782f42aab04..2d074c115dd 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -64,7 +64,7 @@ describe "User Feed" do
end
it 'has XHTML summaries in issue descriptions' do
- expect(body).to match / /
+ expect(body).to match %r{ }
end
it 'has XHTML summaries in notes' do
@@ -72,7 +72,7 @@ describe "User Feed" do
end
it 'has XHTML summaries in merge request descriptions' do
- expect(body).to match /Here is the fix: ]*> ]*\/><\/a>/
+ expect(body).to match %r{Here is the fix: ]*> ]*/> }
end
it 'has push event commit ID' do
diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 61861d33952..19995559fae 100644
--- a/spec/features/merge_request/user_resolves_conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -100,12 +100,12 @@ describe 'Merge request > User resolves conflicts', :js do
end
it 'shows a link to the conflict resolution page' do
- expect(page).to have_link('conflicts', href: /\/conflicts\Z/)
+ expect(page).to have_link('conflicts', href: %r{/conflicts\Z})
end
context 'in Inline view mode' do
before do
- click_link('conflicts', href: /\/conflicts\Z/)
+ click_link('conflicts', href: %r{/conflicts\Z})
end
include_examples "conflicts are resolved in Interactive mode"
@@ -114,7 +114,7 @@ describe 'Merge request > User resolves conflicts', :js do
context 'in Parallel view mode' do
before do
- click_link('conflicts', href: /\/conflicts\Z/)
+ click_link('conflicts', href: %r{/conflicts\Z})
click_button 'Side-by-side'
end
@@ -128,7 +128,7 @@ describe 'Merge request > User resolves conflicts', :js do
before do
visit project_merge_request_path(project, merge_request)
- click_link('conflicts', href: /\/conflicts\Z/)
+ click_link('conflicts', href: %r{/conflicts\Z})
end
it 'conflicts can not be resolved in Interactive mode' do
@@ -181,7 +181,7 @@ describe 'Merge request > User resolves conflicts', :js do
end
it 'does not show a link to the conflict resolution page' do
- expect(page).not_to have_link('conflicts', href: /\/conflicts\Z/)
+ expect(page).not_to have_link('conflicts', href: %r{/conflicts\Z})
end
it 'shows an error if the conflicts page is visited directly' do
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 32432ee1e81..5f608fe18d9 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -105,7 +105,7 @@ describe GroupsHelper do
it 'outputs the groups in the correct order' do
expect(helper.group_title(very_deep_nested_group))
- .to match(/#{deep_nested_group.name}.*<\/li>.*#{very_deep_nested_group.name}<\/a>/m)
+ .to match(%r{#{deep_nested_group.name}.* .*#{very_deep_nested_group.name}}m)
end
end
@@ -120,7 +120,7 @@ describe GroupsHelper do
let(:possible_help_texts) do
{
default_help: "This setting will be applied to all subgroups unless overridden by a group owner",
- ancestor_locked_but_you_can_override: /This setting is applied on .+<\/a>\. You can override the setting or .+/,
+ ancestor_locked_but_you_can_override: %r{This setting is applied on .+ \. You can override the setting or .+},
ancestor_locked_so_ask_the_owner: /This setting is applied on .+\. To share projects in this group with another group, ask the owner to override the setting or remove the share with group lock from .+/,
ancestor_locked_and_has_been_overridden: /This setting is applied on .+ and has been overridden on this subgroup/
}
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0286d36952c..619baa78bfa 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -104,7 +104,7 @@ describe LabelsHelper do
context 'with a tooltip argument' do
context 'set to false' do
it 'does not include the has-tooltip class' do
- expect(link_to_label(label, tooltip: false)).not_to match %r{has-tooltip}
+ expect(link_to_label(label, tooltip: false)).not_to match /has-tooltip/
end
end
end
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index fa8cfda3b86..b5b15726816 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -27,7 +27,7 @@ describe VersionCheckHelper do
end
it 'should have a VersionCheck url as the src' do
- expect(@image_tag).to match(/src="https:\/\/version\.host\.com\/check\.svg\?gitlab_info=xxx"/)
+ expect(@image_tag).to match(%r{src="https://version\.host\.com/check\.svg\?gitlab_info=xxx"})
end
end
end
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 935146c17fc..a41a28a56f1 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -53,7 +53,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
doc = reference_filter("See (#{reference}.)")
exp = Regexp.escape(range.reference_link_text)
- expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{exp}\.\)})
end
it 'ignores invalid commit IDs' do
@@ -222,7 +222,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(range.reference_link_text(project))
- expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{exp}\.\)})
end
it 'ignores invalid commit IDs on the referenced project' do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 080a5f57da9..35f8792ff35 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -42,7 +42,7 @@ describe Banzai::Filter::CommitReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("See (#{reference}.)")
- expect(doc.to_html).to match(/\(#{commit.short_id}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{commit.short_id}\.\)})
end
it 'ignores invalid commit IDs' do
@@ -199,12 +199,12 @@ describe Banzai::Filter::CommitReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
- expect(doc.to_html).to match(/\(#{commit.reference_link_text(project)}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{commit.reference_link_text(project)}\.\)})
end
it 'ignores invalid commit IDs on the referenced project' do
act = "Committed #{invalidate_reference(reference)}"
- expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
+ expect(reference_filter(act).to_html).to match(%r{#{Regexp.escape(invalidate_reference(reference))}})
end
end
end
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index a0d391d981c..d9018a7e4fe 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -49,7 +49,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
it 'links with adjacent text' do
doc = filter("Issue (#{reference}.)")
- expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{reference}\.\)})
end
it 'includes a title attribute' do
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 51920869545..c84b98eb225 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -14,7 +14,7 @@ describe Banzai::Filter::ImageLinkFilter do
it 'does not wrap a duplicate link' do
doc = filter(%Q(#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} ))
- expect(doc.to_html).to match /^ ]*><\/a>$/
+ expect(doc.to_html).to match %r{^ ]*> $}
end
it 'works with external images' do
@@ -24,6 +24,6 @@ describe Banzai::Filter::ImageLinkFilter do
it 'works with inline images' do
doc = filter(%Q(test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline
))
- expect(doc.to_html).to match /^test ]*> ]*><\/a> inline<\/p>$/
+ expect(doc.to_html).to match %r{^test ]*> ]*> inline
$}
end
end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 3a5f52ea23f..905fbb9434b 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -288,7 +288,7 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
- expect(doc.to_html).to match(/\( #{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)\.\)})
end
it 'includes default classes' do
@@ -317,7 +317,7 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference_link}.)")
- expect(doc.to_html).to match(/\(Reference<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(Reference\.\)})
end
it 'includes default classes' do
@@ -346,7 +346,7 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference_link}.)")
- expect(doc.to_html).to match(/\(Reference<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(Reference\.\)})
end
it 'includes default classes' do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 158844e25ae..eeb82822f68 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -42,7 +42,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Merge (#{reference}.)")
- expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{Regexp.escape(reference)}\.\)})
end
it 'ignores invalid merge IDs' do
@@ -211,7 +211,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Merge (#{reference}.)")
- expect(doc.to_html).to match(/\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)\.\)})
end
end
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 3a07a6dc179..e068e02d4fc 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -28,7 +28,7 @@ describe Banzai::Filter::SnippetReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Snippet (#{reference}.)")
- expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{Regexp.escape(reference)}\.\)})
end
it 'ignores invalid snippet IDs' do
@@ -192,13 +192,13 @@ describe Banzai::Filter::SnippetReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("See (#{reference}.)")
- expect(doc.to_html).to match(/\(#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{Regexp.escape(snippet.to_reference(project))}\.\)})
end
it 'ignores invalid snippet IDs on the referenced project' do
act = "See #{invalidate_reference(reference)}"
- expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
+ expect(reference_filter(act).to_html).to match(%r{#{Regexp.escape(invalidate_reference(reference))}})
end
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index c76adc262fc..2f86a046d28 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -146,7 +146,7 @@ describe Banzai::Filter::UserReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
- expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(#{reference}\.\)})
end
it 'includes default classes' do
@@ -172,7 +172,7 @@ describe Banzai::Filter::UserReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
- expect(doc.to_html).to match(/\(User<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(User\.\)})
end
it 'includes a data-user attribute' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index bf01e6ef8e8..57194ec9f0a 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -249,7 +249,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
shared_examples 'archive check' do |extenstion|
- it { expect(metadata['ArchivePath']).to match(/tmp\/gitlab-git-test.git\/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}/) }
+ it { expect(metadata['ArchivePath']).to match(%r{tmp/gitlab-git-test.git/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}}) }
it { expect(metadata['ArchivePath']).to end_with extenstion }
end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 0ae90069b7f..85991c38363 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -121,7 +121,7 @@ describe Gitlab::PathRegex do
STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
- WILDCARD_SEGMENT = %r{\*}
+ WILDCARD_SEGMENT = /\*/
let(:namespaced_wildcard_routes) do
routes_without_format.select do |p|
p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}}
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 7a8e798e3c9..59eda025108 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -1357,7 +1357,7 @@ describe Notify do
matcher :have_part_with do |expected|
match do |actual|
- actual.body.parts.any? { |part| part.content_type.try(:match, %r(#{expected})) }
+ actual.body.parts.any? { |part| part.content_type.try(:match, /#{expected}/) }
end
end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 1eaaadf56c5..748c366efca 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -205,7 +205,7 @@ describe JiraService do
@jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @comment_url).with(
- body: /#{custom_base_url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/
+ body: %r{#{custom_base_url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}}
).once
end
@@ -220,7 +220,7 @@ describe JiraService do
@jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @comment_url).with(
- body: /#{Gitlab.config.gitlab.url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/
+ body: %r{#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}}
).once
end
diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
index f17db70faf6..240aa638f79 100644
--- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
@@ -43,7 +43,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'creates a system note' do
note = merge_request.notes.last
- expect(note.note).to match /enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{8}/
+ expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}}
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index ab3a257f36f..ab3aa18cf4e 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -308,7 +308,7 @@ describe SystemNoteService do
end
it "posts the 'merge when pipeline succeeds' system note" do
- expect(subject.note).to match(/enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{40} succeeds/)
+ expect(subject.note).to match(%r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{40} succeeds})
end
end
@@ -695,7 +695,7 @@ describe SystemNoteService do
commit = double(title: 'This is a test ', short_id: '12345678')
escaped = '<pre>This is a test</pre>'
- expect(described_class.new_commit_summary([commit])).to all(match(%r[- #{escaped}]))
+ expect(described_class.new_commit_summary([commit])).to all(match(/- #{escaped}/))
end
end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index b41c3b3958a..168facd51a6 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -165,7 +165,7 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('pages.tar.gz')
expect(tar_contents).to match('lfs.tar.gz')
expect(tar_contents).to match('registry.tar.gz')
- expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/)
+ expect(tar_contents).not_to match(%r{^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)/$})
end
it 'deletes temp directories' do
diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb
index dacc5dc5ae7..9aebf7b0b4a 100644
--- a/spec/tasks/gitlab/git_rake_spec.rb
+++ b/spec/tasks/gitlab/git_rake_spec.rb
@@ -19,7 +19,7 @@ describe 'gitlab:git rake tasks' do
describe 'fsck' do
it 'outputs the integrity check for a repo' do
- expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity at .*@hashed\/1\/2\/test.git/).to_stdout
+ expect { run_rake_task('gitlab:git:fsck') }.to output(%r{Performed Checking integrity at .*@hashed/1/2/test.git}).to_stdout
end
it 'errors out about config.lock issues' do
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index fd195d6f9b8..845516e5004 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -112,7 +112,7 @@ describe FileUploader do
fixture = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')
uploader.store!(fixture_file_upload(fixture))
- expect(uploader.relative_path).to match(/\A\h{32}\/rails_sample.jpg\z/)
+ expect(uploader.relative_path).to match(%r{\A\h{32}/rails_sample.jpg\z})
end
end
end
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index 98a4373e9d0..a067c3e75f4 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -12,7 +12,7 @@ describe JobArtifactUploader do
context 'when using local storage' do
it { is_expected.to start_with(local_path) }
- it { is_expected.to match(/\h{2}\/\h{2}\/\h{64}\/\d{4}_\d{1,2}_\d{1,2}\/\d+\/\d+\z/) }
+ it { is_expected.to match(%r{\h{2}/\h{2}/\h{64}/\d{4}_\d{1,2}_\d{1,2}/\d+/\d+\z}) }
it { is_expected.to end_with(path) }
end
end
--
cgit v1.2.1
From a2718ebaffd431b1eec91f3ee467d9632309b5f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Fri, 4 Aug 2017 19:14:04 +0200
Subject: Make user/author use project.creator in most factories
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
spec/factories/commits.rb | 2 +-
spec/factories/deployments.rb | 3 ++-
spec/factories/events.rb | 2 +-
spec/factories/issues.rb | 2 +-
spec/factories/merge_requests.rb | 2 +-
spec/factories/notes.rb | 2 +-
spec/factories/project_wikis.rb | 2 +-
spec/factories/sent_notifications.rb | 2 +-
spec/factories/snippets.rb | 1 +
spec/factories/subscriptions.rb | 2 +-
spec/factories/timelogs.rb | 2 +-
spec/factories/todos.rb | 4 ++--
spec/features/dashboard/merge_requests_spec.rb | 22 ++++++++++++----------
.../merge_request/user_awards_emoji_spec.rb | 2 +-
.../lib/gitlab/slash_commands/issue_search_spec.rb | 2 +-
spec/policies/project_policy_spec.rb | 2 +-
spec/services/issues/close_service_spec.rb | 2 +-
spec/services/issues/update_service_spec.rb | 3 ++-
spec/services/merge_requests/close_service_spec.rb | 2 +-
.../merge_requests/ff_merge_service_spec.rb | 3 ++-
.../services/merge_requests/reopen_service_spec.rb | 2 +-
.../services/merge_requests/update_service_spec.rb | 3 ++-
spec/services/notification_service_spec.rb | 6 ++++--
23 files changed, 42 insertions(+), 33 deletions(-)
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index 84a8bc56640..d5d819d862a 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -23,7 +23,7 @@ FactoryBot.define do
end
after(:build) do |commit, evaluator|
- allow(commit).to receive(:author).and_return(evaluator.author || build(:author))
+ allow(commit).to receive(:author).and_return(evaluator.author || build_stubbed(:author))
end
trait :without_author do
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 9d7d5e56611..cac56695319 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -3,13 +3,14 @@ FactoryBot.define do
sha '97de212e80737a608d939f648d959671fb0a0142'
ref 'master'
tag false
- user
+ user nil
project nil
deployable factory: :ci_build
environment factory: :environment
after(:build) do |deployment, evaluator|
deployment.project ||= deployment.environment.project
+ deployment.user ||= deployment.project.creator
unless deployment.project.repository_exists?
allow(deployment.project.repository).to receive(:create_ref)
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index ed275243ac9..5798b81ecad 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :event do
project
- author factory: :user
+ author(factory: :user) { project.creator }
action Event::JOINED
trait(:created) { action Event::CREATED }
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 71dc169c6a2..998080a3dd5 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,8 +1,8 @@
FactoryBot.define do
factory :issue do
title { generate(:title) }
- author
project
+ author { project.creator }
trait :confidential do
confidential true
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 40558c88d15..d26cb0c3417 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,9 +1,9 @@
FactoryBot.define do
factory :merge_request do
title { generate(:title) }
- author
association :source_project, :repository, factory: :project
target_project { source_project }
+ author { source_project.creator }
# $ git log --pretty=oneline feature..master
# 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 707ecbd6be5..2defb4935ad 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -6,7 +6,7 @@ FactoryBot.define do
factory :note do
project
note { generate(:title) }
- author
+ author { project&.creator || create(:user) }
on_issue
factory :note_on_commit, traits: [:on_commit]
diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb
index 89d8248f9f4..db2eb4fc863 100644
--- a/spec/factories/project_wikis.rb
+++ b/spec/factories/project_wikis.rb
@@ -3,7 +3,7 @@ FactoryBot.define do
skip_create
project
- user factory: :user
+ user { project.creator }
initialize_with { new(project, user) }
end
end
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
index 80872067233..b0174dd06b7 100644
--- a/spec/factories/sent_notifications.rb
+++ b/spec/factories/sent_notifications.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :sent_notification do
project
- recipient factory: :user
+ recipient { project.creator }
noteable { create(:issue, project: project) }
reply_key { SentNotification.reply_key }
end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 2ab9a56d255..dc12b562108 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -21,6 +21,7 @@ FactoryBot.define do
factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
project
+ author { project.creator }
end
factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb
index a4bc4e87b0a..8f7ab74ec70 100644
--- a/spec/factories/subscriptions.rb
+++ b/spec/factories/subscriptions.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :subscription do
- user
project
+ user { project.creator }
subscribable factory: :issue
end
end
diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb
index af34b0681e2..b45f06b9a0a 100644
--- a/spec/factories/timelogs.rb
+++ b/spec/factories/timelogs.rb
@@ -3,7 +3,7 @@
FactoryBot.define do
factory :timelog do
time_spent 3600
- user
issue
+ user { issue.project.creator }
end
end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 6a6de665dd1..94f8caedfa6 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,8 +1,8 @@
FactoryBot.define do
factory :todo do
project
- author
- user
+ author { project.creator }
+ user { project.creator }
target factory: :issue
action { Todo::ASSIGNED }
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 991d360ccaf..744041ac425 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -44,36 +44,38 @@ feature 'Dashboard Merge Requests' do
context 'merge requests exist' do
let!(:assigned_merge_request) do
- create(:merge_request, assignee: current_user, target_project: project, source_project: project)
+ create(:merge_request,
+ assignee: current_user,
+ source_project: project,
+ author: create(:user))
end
let!(:assigned_merge_request_from_fork) do
create(:merge_request,
source_branch: 'markdown', assignee: current_user,
- target_project: public_project, source_project: forked_project
- )
+ target_project: public_project, source_project: forked_project,
+ author: create(:user))
end
let!(:authored_merge_request) do
create(:merge_request,
- source_branch: 'markdown', author: current_user,
- target_project: project, source_project: project
- )
+ source_branch: 'markdown',
+ source_project: project,
+ author: current_user)
end
let!(:authored_merge_request_from_fork) do
create(:merge_request,
source_branch: 'feature_conflict',
author: current_user,
- target_project: public_project, source_project: forked_project
- )
+ target_project: public_project, source_project: forked_project)
end
let!(:other_merge_request) do
create(:merge_request,
source_branch: 'fix',
- target_project: project, source_project: project
- )
+ source_project: project,
+ author: create(:user))
end
before do
diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index 15a0878fb16..2f24cfbd9e3 100644
--- a/spec/features/merge_request/user_awards_emoji_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe 'Merge request > User awards emoji', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
- let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project, author: create(:user)) }
describe 'logged in' do
before do
diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
index e41e5254dde..35d01efc1bd 100644
--- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::SlashCommands::IssueSearch do
let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
let(:project) { create(:project) }
- let(:user) { issue.author }
+ let(:user) { create(:user) }
let(:regex_match) { described_class.match("issue search find") }
subject do
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index f2593a1a75c..129344f105f 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -92,7 +92,7 @@ describe ProjectPolicy do
it 'does not include the read_issue permission when the issue author is not a member of the private project' do
project = create(:project, :private)
- issue = create(:issue, project: project)
+ issue = create(:issue, project: project, author: create(:user))
user = issue.author
expect(project.team.member?(issue.author)).to be false
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 8897a64a138..47c1ebbeb81 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -4,7 +4,7 @@ describe Issues::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:issue) { create(:issue, assignees: [user2]) }
+ let(:issue) { create(:issue, assignees: [user2], author: create(:user)) }
let(:project) { issue.project }
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 1cb6f2e097f..41237dd7160 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -13,7 +13,8 @@ describe Issues::UpdateService, :mailer do
create(:issue, title: 'Old title',
description: "for #{user2.to_reference}",
assignee_ids: [user3.id],
- project: project)
+ project: project,
+ author: create(:user))
end
before do
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 4d12de3ecce..216e0cd4266 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -4,7 +4,7 @@ describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:merge_request) { create(:merge_request, assignee: user2) }
+ let(:merge_request) { create(:merge_request, assignee: user2, author: create(:user)) }
let(:project) { merge_request.project }
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index aa90feeef89..5ef6365fcc9 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -7,7 +7,8 @@ describe MergeRequests::FfMergeService do
create(:merge_request,
source_branch: 'flatten-dir',
target_branch: 'improve/awesome',
- assignee: user2)
+ assignee: user2,
+ author: create(:user))
end
let(:project) { merge_request.project }
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index a44d63e5f9f..9ee37c51d95 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -4,7 +4,7 @@ describe MergeRequests::ReopenService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:merge_request) { create(:merge_request, :closed, assignee: user2) }
+ let(:merge_request) { create(:merge_request, :closed, assignee: user2, author: create(:user)) }
let(:project) { merge_request.project }
before do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2238da2d14d..c31259239ee 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -12,7 +12,8 @@ describe MergeRequests::UpdateService, :mailer do
create(:merge_request, :simple, title: 'Old title',
description: "FYI #{user2.to_reference}",
assignee_id: user3.id,
- source_project: project)
+ source_project: project,
+ author: create(:user))
end
before do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 5c59455e3e1..35eb84e5e88 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -458,7 +458,7 @@ describe NotificationService, :mailer do
context "merge request diff note" do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: project, assignee: user) }
+ let(:merge_request) { create(:merge_request, source_project: project, assignee: user, author: create(:user)) }
let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
before do
@@ -469,11 +469,13 @@ describe NotificationService, :mailer do
describe '#new_note' do
it "records sent notifications" do
- # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note
+ # 3 SentNotification are sent: the MR assignee and author, and the @u_watcher
expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(3).times.and_call_original
notification.new_note(note)
+ expect(SentNotification.last(3).map(&:recipient).map(&:id))
+ .to contain_exactly(merge_request.assignee.id, merge_request.author.id, @u_watcher.id)
expect(SentNotification.last.in_reply_to_discussion_id).to eq(note.discussion_id)
end
end
--
cgit v1.2.1
From c6311207eef6cf66f77d410269efc7581288cb2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Wed, 31 Jan 2018 18:30:31 +0100
Subject: Fix a JSON schema that doesn't include enough fields
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
spec/fixtures/api/schemas/public_api/v4/user/basic.json | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/basic.json b/spec/fixtures/api/schemas/public_api/v4/user/basic.json
index bf330d8278c..2d815be32a6 100644
--- a/spec/fixtures/api/schemas/public_api/v4/user/basic.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/basic.json
@@ -2,12 +2,16 @@
"type": ["object", "null"],
"required": [
"id",
+ "name",
+ "username",
"state",
"avatar_url",
"web_url"
],
"properties": {
"id": { "type": "integer" },
+ "name": { "type": "string" },
+ "username": { "type": "string" },
"state": { "type": "string" },
"avatar_url": { "type": "string" },
"web_url": { "type": "string" }
--
cgit v1.2.1
From 0f895147ff52d71c7ced5db536d2be6154839112 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Wed, 31 Jan 2018 17:51:53 +0000
Subject: fixed issue_spec transient failure
---
spec/javascripts/issue_spec.js | 59 +++++++++++++++++++-----------------------
1 file changed, 27 insertions(+), 32 deletions(-)
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 7eaaa0ff027..5245bf6455c 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -92,21 +92,6 @@ describe('Issue', function() {
const action = isIssueInitiallyOpen ? 'close' : 'reopen';
let mock;
- function ajaxSpy(req) {
- if (req.url === this.$triggeredButton.attr('href')) {
- expect(req.type).toBe('PUT');
- expectNewBranchButtonState(true, false);
- return this.issueStateDeferred;
- } else if (req.url === Issue.createMrDropdownWrap.dataset.canCreatePath) {
- expect(req.type).toBe('GET');
- expectNewBranchButtonState(true, false);
- return this.canCreateBranchDeferred;
- }
-
- expect(req.url).toBe('unexpected');
- return null;
- }
-
function mockCloseButtonResponseSuccess(url, response) {
mock.onPut(url).reply(() => {
expectNewBranchButtonState(true, false);
@@ -119,6 +104,12 @@ describe('Issue', function() {
mock.onPut(url).networkError();
}
+ function mockCanCreateBranch(canCreateBranch) {
+ mock.onGet(/(.*)\/can_create_branch$/).reply(200, {
+ can_create_branch: canCreateBranch,
+ });
+ }
+
beforeEach(function() {
if (isIssueInitiallyOpen) {
loadFixtures('issues/open-issue.html.raw');
@@ -126,6 +117,11 @@ describe('Issue', function() {
loadFixtures('issues/closed-issue.html.raw');
}
+ mock = new MockAdapter(axios);
+
+ mock.onGet(/(.*)\/related_branches$/).reply(200, {});
+ mock.onGet(/(.*)\/referenced_merge_requests$/).reply(200, {});
+
findElements(isIssueInitiallyOpen);
this.issue = new Issue();
expectIssueState(isIssueInitiallyOpen);
@@ -135,27 +131,21 @@ describe('Issue', function() {
this.$projectIssuesCounter = $('.issue_counter').first();
this.$projectIssuesCounter.text('1,001');
- this.issueStateDeferred = new jQuery.Deferred();
- this.canCreateBranchDeferred = new jQuery.Deferred();
-
- mock = new MockAdapter(axios);
-
- spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this));
+ spyOn(axios, 'get').and.callThrough();
});
afterEach(() => {
mock.restore();
+ $('div.flash-alert').remove();
});
it(`${action}s the issue`, function(done) {
mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
id: 34
});
+ mockCanCreateBranch(!isIssueInitiallyOpen);
this.$triggeredButton.trigger('click');
- this.canCreateBranchDeferred.resolve({
- can_create_branch: !isIssueInitiallyOpen
- });
setTimeout(() => {
expectIssueState(!isIssueInitiallyOpen);
@@ -171,10 +161,9 @@ describe('Issue', function() {
mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
saved: false
});
+ mockCanCreateBranch(isIssueInitiallyOpen);
+
this.$triggeredButton.trigger('click');
- this.canCreateBranchDeferred.resolve({
- can_create_branch: isIssueInitiallyOpen
- });
setTimeout(() => {
expectIssueState(isIssueInitiallyOpen);
@@ -189,10 +178,9 @@ describe('Issue', function() {
it(`fails to ${action} the issue if HTTP error occurs`, function(done) {
mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
+ mockCanCreateBranch(isIssueInitiallyOpen);
+
this.$triggeredButton.trigger('click');
- this.canCreateBranchDeferred.resolve({
- can_create_branch: isIssueInitiallyOpen
- });
setTimeout(() => {
expectIssueState(isIssueInitiallyOpen);
@@ -207,18 +195,25 @@ describe('Issue', function() {
it('disables the new branch button if Ajax call fails', function() {
mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
+ mock.onGet(/(.*)\/can_create_branch$/).networkError();
+
this.$triggeredButton.trigger('click');
- this.canCreateBranchDeferred.reject();
expectNewBranchButtonState(false, false);
});
- it('does not trigger Ajax call if new branch button is missing', function() {
+ it('does not trigger Ajax call if new branch button is missing', function(done) {
mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
Issue.$btnNewBranch = $();
this.canCreateBranchDeferred = null;
this.$triggeredButton.trigger('click');
+
+ setTimeout(() => {
+ expect(axios.get).not.toHaveBeenCalled();
+
+ done();
+ });
});
});
});
--
cgit v1.2.1
From 27863b10d110e38b0ae7bfd1fefc0cc32646b748 Mon Sep 17 00:00:00 2001
From: Nick Thomas
Date: Wed, 31 Jan 2018 17:53:09 +0000
Subject: Refactor repository_storages_options_for_select
---
app/helpers/application_settings_helper.rb | 4 ++--
app/views/admin/application_settings/_form.html.haml | 3 ++-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 8ef561d90e6..e91e1d29d64 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -96,12 +96,12 @@ module ApplicationSettingsHelper
]
end
- def repository_storages_options_for_select
+ def repository_storages_options_for_select(selected)
options = Gitlab.config.repositories.storages.map do |name, storage|
["#{name} - #{storage['path']}", name]
end
- options_for_select(options, @application_setting.repository_storages)
+ options_for_select(options, selected)
end
def sidekiq_queue_options_for_select
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index ba4ca88a8a9..fb5e6f337a7 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -537,7 +537,8 @@
.form-group
= f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2'
.col-sm-10
- = f.select :repository_storages, repository_storages_options_for_select, {include_hidden: false}, multiple: true, class: 'form-control'
+ = f.select :repository_storages, repository_storages_options_for_select(@application_setting.repository_storages),
+ {include_hidden: false}, multiple: true, class: 'form-control'
.help-block
Manage repository storage paths. Learn more in the
= succeed "." do
--
cgit v1.2.1
From 332aa756d20e8388fd402fc5c3acbf504f4cf09a Mon Sep 17 00:00:00 2001
From: Eric Eastwood
Date: Fri, 26 Jan 2018 00:25:26 -0600
Subject: Refactor CI variable list code for usage with CI/CD settings page
secret variables
Part of https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4110
---
app/assets/javascripts/behaviors/secret_values.js | 8 +-
.../ci_variable_list/ci_variable_list.js | 205 +++++++++++++++++++++
.../ci_variable_list/native_form_variable_list.js | 26 +++
app/assets/javascripts/commons/polyfills.js | 2 +
.../pipeline_schedule_form_bundle.js | 7 +-
.../setup_pipeline_variable_list.js | 73 --------
app/assets/javascripts/toggle_buttons.js | 8 +-
app/assets/stylesheets/framework.scss | 1 +
app/assets/stylesheets/framework/buttons.scss | 5 +
.../stylesheets/framework/ci_variable_list.scss | 88 +++++++++
app/assets/stylesheets/framework/variables.scss | 4 +-
.../stylesheets/pages/pipeline_schedules.scss | 81 --------
app/views/ci/variables/_variable_row.html.haml | 49 +++++
.../projects/pipeline_schedules/_form.html.haml | 16 +-
.../pipeline_schedules/_variable_row.html.haml | 17 --
...r-ci-variable-list-for-future-usage-in-4110.yml | 5 +
spec/features/projects/pipeline_schedules_spec.rb | 30 +--
.../ci_variable_list/ci_variable_list_spec.js | 163 ++++++++++++++++
.../native_form_variable_list_spec.js | 30 +++
spec/javascripts/fixtures/pipeline_schedules.rb | 43 +++++
.../setup_pipeline_variable_list_spec.js | 145 ---------------
21 files changed, 660 insertions(+), 346 deletions(-)
create mode 100644 app/assets/javascripts/ci_variable_list/ci_variable_list.js
create mode 100644 app/assets/javascripts/ci_variable_list/native_form_variable_list.js
delete mode 100644 app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
create mode 100644 app/assets/stylesheets/framework/ci_variable_list.scss
create mode 100644 app/views/ci/variables/_variable_row.html.haml
delete mode 100644 app/views/projects/pipeline_schedules/_variable_row.html.haml
create mode 100644 changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml
create mode 100644 spec/javascripts/ci_variable_list/ci_variable_list_spec.js
create mode 100644 spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
create mode 100644 spec/javascripts/fixtures/pipeline_schedules.rb
delete mode 100644 spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js
index 7f70fce913a..0d6e0dbefcc 100644
--- a/app/assets/javascripts/behaviors/secret_values.js
+++ b/app/assets/javascripts/behaviors/secret_values.js
@@ -15,10 +15,12 @@ export default class SecretValues {
init() {
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
- const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
- this.updateDom(isRevealed);
+ if (this.revealButton) {
+ const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
+ this.updateDom(isRevealed);
- this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
+ this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
+ }
}
onRevealButtonClicked() {
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
new file mode 100644
index 00000000000..e46478ddb98
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -0,0 +1,205 @@
+import $ from 'jquery';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { s__ } from '../locale';
+import setupToggleButtons from '../toggle_buttons';
+import CreateItemDropdown from '../create_item_dropdown';
+import SecretValues from '../behaviors/secret_values';
+
+const ALL_ENVIRONMENTS_STRING = s__('CiVariable|All environments');
+
+function createEnvironmentItem(value) {
+ return {
+ title: value === '*' ? ALL_ENVIRONMENTS_STRING : value,
+ id: value,
+ text: value,
+ };
+}
+
+export default class VariableList {
+ constructor({
+ container,
+ formField,
+ }) {
+ this.$container = $(container);
+ this.formField = formField;
+ this.environmentDropdownMap = new WeakMap();
+
+ this.inputMap = {
+ id: {
+ selector: '.js-ci-variable-input-id',
+ default: '',
+ },
+ key: {
+ selector: '.js-ci-variable-input-key',
+ default: '',
+ },
+ value: {
+ selector: '.js-ci-variable-input-value',
+ default: '',
+ },
+ protected: {
+ selector: '.js-ci-variable-input-protected',
+ default: 'true',
+ },
+ environment: {
+ // We can't use a `.js-` class here because
+ // gl_dropdown replaces the and doesn't copy over the class
+ // See https://gitlab.com/gitlab-org/gitlab-ce/issues/42458
+ selector: `input[name="${this.formField}[variables_attributes][][environment]"]`,
+ default: '*',
+ },
+ _destroy: {
+ selector: '.js-ci-variable-input-destroy',
+ default: '',
+ },
+ };
+
+ this.secretValues = new SecretValues({
+ container: this.$container[0],
+ valueSelector: '.js-row:not(:last-child) .js-secret-value',
+ placeholderSelector: '.js-row:not(:last-child) .js-secret-value-placeholder',
+ });
+ }
+
+ init() {
+ this.bindEvents();
+ this.secretValues.init();
+ }
+
+ bindEvents() {
+ this.$container.find('.js-row').each((index, rowEl) => {
+ this.initRow(rowEl);
+ });
+
+ this.$container.on('click', '.js-row-remove-button', (e) => {
+ e.preventDefault();
+ this.removeRow($(e.currentTarget).closest('.js-row'));
+ });
+
+ const inputSelector = Object.keys(this.inputMap)
+ .map(name => this.inputMap[name].selector)
+ .join(',');
+
+ // Remove any empty rows except the last row
+ this.$container.on('blur', inputSelector, (e) => {
+ const $row = $(e.currentTarget).closest('.js-row');
+
+ if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) {
+ this.removeRow($row);
+ }
+ });
+
+ // Always make sure there is an empty last row
+ this.$container.on('input trigger-change', inputSelector, () => {
+ const $lastRow = this.$container.find('.js-row').last();
+
+ if (this.checkIfRowTouched($lastRow)) {
+ this.insertRow($lastRow);
+ }
+ });
+ }
+
+ initRow(rowEl) {
+ const $row = $(rowEl);
+
+ setupToggleButtons($row[0]);
+
+ const $environmentSelect = $row.find('.js-variable-environment-toggle');
+ if ($environmentSelect.length) {
+ const createItemDropdown = new CreateItemDropdown({
+ $dropdown: $environmentSelect,
+ defaultToggleLabel: ALL_ENVIRONMENTS_STRING,
+ fieldName: `${this.formField}[variables_attributes][][environment]`,
+ getData: (term, callback) => callback(this.getEnvironmentValues()),
+ createNewItemFromValue: createEnvironmentItem,
+ onSelect: () => {
+ // Refresh the other dropdowns in the variable list
+ // so they have the new value we just picked
+ this.refreshDropdownData();
+
+ $row.find(this.inputMap.environment.selector).trigger('trigger-change');
+ },
+ });
+
+ // Clear out any data that might have been left-over from the row clone
+ createItemDropdown.clearDropdown();
+
+ this.environmentDropdownMap.set($row[0], createItemDropdown);
+ }
+ }
+
+ insertRow($row) {
+ const $rowClone = $row.clone();
+ $rowClone.removeAttr('data-is-persisted');
+
+ // Reset the inputs to their defaults
+ Object.keys(this.inputMap).forEach((name) => {
+ const entry = this.inputMap[name];
+ $rowClone.find(entry.selector).val(entry.default);
+ });
+
+ this.initRow($rowClone);
+
+ $row.after($rowClone);
+ }
+
+ removeRow($row) {
+ const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
+
+ if (isPersisted) {
+ $row.hide();
+ $row
+ // eslint-disable-next-line no-underscore-dangle
+ .find(this.inputMap._destroy.selector)
+ .val(true);
+ } else {
+ $row.remove();
+ }
+ }
+
+ checkIfRowTouched($row) {
+ return Object.keys(this.inputMap).some((name) => {
+ const entry = this.inputMap[name];
+ const $el = $row.find(entry.selector);
+ return $el.length && $el.val() !== entry.default;
+ });
+ }
+
+ getAllData() {
+ // Ignore the last empty row because we don't want to try persist
+ // a blank variable and run into validation problems.
+ const validRows = this.$container.find('.js-row').toArray().slice(0, -1);
+
+ return validRows.map((rowEl) => {
+ const resultant = {};
+ Object.keys(this.inputMap).forEach((name) => {
+ const entry = this.inputMap[name];
+ const $input = $(rowEl).find(entry.selector);
+ if ($input.length) {
+ resultant[name] = $input.val();
+ }
+ });
+
+ return resultant;
+ });
+ }
+
+ getEnvironmentValues() {
+ const valueMap = this.$container.find(this.inputMap.environment.selector).toArray()
+ .reduce((prevValueMap, envInput) => ({
+ ...prevValueMap,
+ [envInput.value]: envInput.value,
+ }), {});
+
+ return Object.keys(valueMap).map(createEnvironmentItem);
+ }
+
+ refreshDropdownData() {
+ this.$container.find('.js-row').each((index, rowEl) => {
+ const environmentDropdown = this.environmentDropdownMap.get(rowEl);
+ if (environmentDropdown) {
+ environmentDropdown.refreshData();
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
new file mode 100644
index 00000000000..d54ea7df1c3
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
@@ -0,0 +1,26 @@
+import VariableList from './ci_variable_list';
+
+// Used for the variable list on scheduled pipeline edit page
+export default function setupNativeFormVariableList({
+ container,
+ formField = 'variables',
+}) {
+ const $container = $(container);
+
+ const variableList = new VariableList({
+ container: $container,
+ formField,
+ });
+ variableList.init();
+
+ // Clear out the names in the empty last row so it
+ // doesn't get submitted and throw validation errors
+ $container.closest('form').on('submit trigger-submit', () => {
+ const $lastRow = $container.find('.js-row').last();
+
+ const isTouched = variableList.checkIfRowTouched($lastRow);
+ if (!isTouched) {
+ $lastRow.find('input, textarea').attr('name', '');
+ }
+ });
+}
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index ff9e4485916..46232726510 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -8,6 +8,8 @@ import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
import 'core-js/fn/symbol';
+import 'core-js/es6/map';
+import 'core-js/es6/weak-map';
// Browser polyfills
import 'classlist-polyfill';
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
index f1cf6e92ef5..0b1a81bae13 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
@@ -4,7 +4,7 @@ import GlFieldErrors from '../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
-import { setupPipelineVariableList } from './setup_pipeline_variable_list';
+import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list';
Vue.use(Translate);
@@ -42,5 +42,8 @@ document.addEventListener('DOMContentLoaded', () => {
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
- setupPipelineVariableList($('.js-pipeline-variable-list'));
+ setupNativeFormVariableList({
+ container: $('.js-ci-variable-list-section'),
+ formField: 'schedule',
+ });
});
diff --git a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
deleted file mode 100644
index 9e0e5cacb11..00000000000
--- a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
-
-function insertRow($row) {
- const $rowClone = $row.clone();
- $rowClone.removeAttr('data-is-persisted');
- $rowClone.find('input, textarea').val('');
- $row.after($rowClone);
-}
-
-function removeRow($row) {
- const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
-
- if (isPersisted) {
- $row.hide();
- $row
- .find('.js-destroy-input')
- .val(1);
- } else {
- $row.remove();
- }
-}
-
-function checkIfRowTouched($row) {
- return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
-}
-
-function setupPipelineVariableList(parent = document) {
- const $parent = $(parent);
-
- $parent.on('click', '.js-row-remove-button', (e) => {
- const $row = $(e.currentTarget).closest('.js-row');
- removeRow($row);
-
- e.preventDefault();
- });
-
- // Remove any empty rows except the last r
- $parent.on('blur', '.js-user-input', (e) => {
- const $row = $(e.currentTarget).closest('.js-row');
-
- const isTouched = checkIfRowTouched($row);
- if ($row.is(':not(:last-child)') && !isTouched) {
- removeRow($row);
- }
- });
-
- // Always make sure there is an empty last row
- $parent.on('input', '.js-user-input', () => {
- const $lastRow = $parent.find('.js-row').last();
-
- const isTouched = checkIfRowTouched($lastRow);
- if (isTouched) {
- insertRow($lastRow);
- }
- });
-
- // Clear out the empty last row so it
- // doesn't get submitted and throw validation errors
- $parent.closest('form').on('submit', () => {
- const $lastRow = $parent.find('.js-row').last();
-
- const isTouched = checkIfRowTouched($lastRow);
- if (!isTouched) {
- $lastRow.find('input, textarea').attr('name', '');
- }
- });
-}
-
-export {
- setupPipelineVariableList,
- insertRow,
- removeRow,
-};
diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js
index 974dc3ee052..2d680d0f0dc 100644
--- a/app/assets/javascripts/toggle_buttons.js
+++ b/app/assets/javascripts/toggle_buttons.js
@@ -13,7 +13,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils';
```
*/
-function updatetoggle(toggle, isOn) {
+function updateToggle(toggle, isOn) {
toggle.classList.toggle('is-checked', isOn);
}
@@ -21,7 +21,7 @@ function onToggleClicked(toggle, input, clickCallback) {
const previousIsOn = convertPermissionToBoolean(input.value);
// Visually change the toggle and start loading
- updatetoggle(toggle, !previousIsOn);
+ updateToggle(toggle, !previousIsOn);
toggle.setAttribute('disabled', true);
toggle.classList.toggle('is-loading', true);
@@ -32,7 +32,7 @@ function onToggleClicked(toggle, input, clickCallback) {
})
.catch(() => {
// Revert the visuals if something goes wrong
- updatetoggle(toggle, previousIsOn);
+ updateToggle(toggle, previousIsOn);
})
.then(() => {
// Remove the loading indicator in any case
@@ -54,7 +54,7 @@ export default function setupToggleButtons(container, clickCallback = () => {})
const isOn = convertPermissionToBoolean(input.value);
// Get the visible toggle in sync with the hidden input
- updatetoggle(toggle, isOn);
+ updateToggle(toggle, isOn);
toggle.addEventListener('click', onToggleClicked.bind(null, toggle, input, clickCallback));
});
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index cff47ea76ec..c4aad24e9c1 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -60,3 +60,4 @@
@import "framework/memory_graph";
@import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
+@import "framework/ci_variable_list";
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index d0b0c69b18f..c4b046a6d68 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -176,6 +176,11 @@
&.btn-remove {
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
}
+
+ &.btn-primary,
+ &.btn-info {
+ @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
+ }
}
&.btn-gray {
diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss
new file mode 100644
index 00000000000..8f654ab363c
--- /dev/null
+++ b/app/assets/stylesheets/framework/ci_variable_list.scss
@@ -0,0 +1,88 @@
+.ci-variable-list {
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+ clear: both;
+}
+
+.ci-variable-row {
+ display: flex;
+ align-items: flex-end;
+
+ &:not(:last-child) {
+ margin-bottom: $gl-btn-padding;
+
+ @media (max-width: $screen-xs-max) {
+ margin-bottom: 3 * $gl-btn-padding;
+ }
+ }
+
+ &:last-child {
+ .ci-variable-body-item:last-child {
+ margin-right: $ci-variable-remove-button-width;
+
+ @media (max-width: $screen-xs-max) {
+ margin-right: 0;
+ }
+ }
+
+ .ci-variable-row-remove-button {
+ display: none;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .ci-variable-row-body {
+ margin-right: $ci-variable-remove-button-width;
+ }
+ }
+ }
+}
+
+.ci-variable-row-body {
+ display: flex;
+ width: 100%;
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+ }
+}
+
+.ci-variable-body-item {
+ flex: 1;
+
+ &:not(:last-child) {
+ margin-right: $gl-btn-padding;
+
+ @media (max-width: $screen-xs-max) {
+ margin-right: 0;
+ margin-bottom: $gl-btn-padding;
+ }
+ }
+}
+
+.ci-variable-protected-item {
+ flex: 0 1 auto;
+ display: flex;
+ align-items: center;
+}
+
+.ci-variable-row-remove-button {
+ @include transition(color);
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: $ci-variable-remove-button-width;
+ height: $input-height;
+ padding: 0;
+ background: transparent;
+ border: 0;
+ color: $gl-text-color-secondary;
+
+ &:hover,
+ &:focus {
+ outline: none;
+ color: $gl-text-color;
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f76c6866463..1cc22f5658d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -668,9 +668,9 @@ $pipeline-dropdown-line-height: 20px;
$pipeline-dropdown-status-icon-size: 18px;
/*
-Pipeline Schedules
+CI variable lists
*/
-$pipeline-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
+$ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/*
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index b698a4f9afa..bc7fa8a26d9 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -78,84 +78,3 @@
margin-right: 3px;
}
}
-
-.pipeline-variable-list {
- margin-left: 0;
- margin-bottom: 0;
- padding-left: 0;
- list-style: none;
- clear: both;
-}
-
-.pipeline-variable-row {
- display: flex;
- align-items: flex-end;
-
- &:not(:last-child) {
- margin-bottom: $gl-btn-padding;
- }
-
- @media (max-width: $screen-sm-max) {
- padding-right: $gl-col-padding;
- }
-
- &:last-child {
- .pipeline-variable-row-remove-button {
- display: none;
- }
-
- @media (max-width: $screen-sm-max) {
- .pipeline-variable-value-input {
- margin-right: $pipeline-variable-remove-button-width;
- }
- }
-
- @media (max-width: $screen-xs-max) {
- .pipeline-variable-row-body {
- margin-right: $pipeline-variable-remove-button-width;
- }
- }
- }
-}
-
-.pipeline-variable-row-body {
- display: flex;
- width: calc(75% - #{$gl-col-padding});
- padding-left: $gl-col-padding;
-
- @media (max-width: $screen-sm-max) {
- width: 100%;
- }
-
- @media (max-width: $screen-xs-max) {
- display: block;
- }
-}
-
-.pipeline-variable-key-input {
- margin-right: $gl-btn-padding;
-
- @media (max-width: $screen-xs-max) {
- margin-bottom: $gl-btn-padding;
- }
-}
-
-.pipeline-variable-row-remove-button {
- @include transition(color);
- flex-shrink: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- width: $pipeline-variable-remove-button-width;
- height: $input-height;
- padding: 0;
- background: transparent;
- border: 0;
- color: $gl-text-color-secondary;
-
- &:hover,
- &:focus {
- outline: none;
- color: $gl-text-color;
- }
-}
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
new file mode 100644
index 00000000000..495a55660cb
--- /dev/null
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -0,0 +1,49 @@
+- form_field = local_assigns.fetch(:form_field, nil)
+- variable = local_assigns.fetch(:variable, nil)
+- only_key_value = local_assigns.fetch(:only_key_value, false)
+
+- id = variable&.id
+- key = variable&.key
+- value = variable&.value
+- is_protected = variable && !only_key_value ? variable.protected : true
+
+- id_input_name = "#{form_field}[variables_attributes][][id]"
+- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
+- key_input_name = "#{form_field}[variables_attributes][][key]"
+- value_input_name = "#{form_field}[variables_attributes][][value]"
+- protected_input_name = "#{form_field}[variables_attributes][][protected]"
+
+%li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
+ .ci-variable-row-body
+ %input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
+ %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
+ %input.js-ci-variable-input-key.ci-variable-body-item.form-control{ type: "text",
+ name: key_input_name,
+ value: key,
+ placeholder: s_('CiVariables|Input variable key') }
+ .ci-variable-body-item
+ .form-control.js-secret-value-placeholder{ class: ('hide' unless id) }
+ = '*' * 20
+ %textarea.js-ci-variable-input-value.js-secret-value.form-control{ class: ('hide' if id),
+ rows: 1,
+ name: value_input_name,
+ placeholder: s_('CiVariables|Input variable value') }
+ = value
+ - unless only_key_value
+ .ci-variable-body-item.ci-variable-protected-item
+ .append-right-default
+ = s_("CiVariable|Protected")
+ %button{ type: 'button',
+ class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if is_protected}",
+ "aria-label": s_("CiVariable|Toggle protected") }
+ %input{ type: "hidden",
+ class: 'js-ci-variable-input-protected js-project-feature-toggle-input',
+ name: protected_input_name,
+ value: is_protected }
+ %span.toggle-icon
+ = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
+ = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
+ -# EE-specific start
+ -# EE-specific end
+ %button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
+ = icon('minus-circle')
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 857ae00d0ab..ff440e99042 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -22,14 +22,20 @@
= f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
- .form-group
+ .form-group.js-ci-variable-list-section
.col-md-9
%label.label-light
#{ s_('PipelineSchedules|Variables') }
- %ul.js-pipeline-variable-list.pipeline-variable-list
- - @schedule.variables.each do |variable|
- = render 'variable_row', id: variable.id, key: variable.key, value: variable.value
- = render 'variable_row'
+ %ul.ci-variable-list
+ - @schedule.variables.each do |variable|
+ = render 'ci/variables/variable_row', form_field: 'schedule', variable: variable, only_key_value: true
+ = render 'ci/variables/variable_row', form_field: 'schedule', only_key_value: true
+ - if @schedule.variables.size > 0
+ %button.btn.btn-info.btn-inverted.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@schedule.variables.size == 0}" } }
+ - if @schedule.variables.size == 0
+ = n_('Hide value', 'Hide values', @schedule.variables.size)
+ - else
+ = n_('Reveal value', 'Reveal values', @schedule.variables.size)
.form-group
.col-md-9
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
diff --git a/app/views/projects/pipeline_schedules/_variable_row.html.haml b/app/views/projects/pipeline_schedules/_variable_row.html.haml
deleted file mode 100644
index 564cb5d1ca9..00000000000
--- a/app/views/projects/pipeline_schedules/_variable_row.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- id = local_assigns.fetch(:id, nil)
-- key = local_assigns.fetch(:key, "")
-- value = local_assigns.fetch(:value, "")
-%li.js-row.pipeline-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
- .pipeline-variable-row-body
- %input{ type: "hidden", name: "schedule[variables_attributes][][id]", value: id }
- %input.js-destroy-input{ type: "hidden", name: "schedule[variables_attributes][][_destroy]" }
- %input.js-user-input.pipeline-variable-key-input.form-control{ type: "text",
- name: "schedule[variables_attributes][][key]",
- value: key,
- placeholder: s_('PipelineSchedules|Input variable key') }
- %textarea.js-user-input.pipeline-variable-value-input.form-control{ rows: 1,
- name: "schedule[variables_attributes][][value]",
- placeholder: s_('PipelineSchedules|Input variable value') }
- = value
- %button.js-row-remove-button.pipeline-variable-row-remove-button{ 'aria-label': s_('PipelineSchedules|Remove variable row') }
- %i.fa.fa-minus-circle{ 'aria-hidden': "true" }
diff --git a/changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml b/changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml
new file mode 100644
index 00000000000..d43675e175d
--- /dev/null
+++ b/changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml
@@ -0,0 +1,5 @@
+---
+title: Hide variable values on pipeline schedule edit page
+merge_request: 16729
+author:
+type: changed
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index fa2f7a1fd78..65e24862d43 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -168,11 +168,11 @@ feature 'Pipeline Schedules', :js do
scenario 'user sees the new variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- page.within('.pipeline-variable-list') do
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('AAA')
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('AAA123')
- expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-key-input").value).to eq('BBB')
- expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-value-input").value).to eq('BBB123')
+ page.within('.ci-variable-list') do
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('AAA')
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('AAA123')
+ expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-key").value).to eq('BBB')
+ expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-value", visible: false).value).to eq('BBB123')
end
end
end
@@ -185,16 +185,18 @@ feature 'Pipeline Schedules', :js do
visit_pipelines_schedules
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- all('[name="schedule[variables_attributes][][key]"]')[0].set('foo')
- all('[name="schedule[variables_attributes][][value]"]')[0].set('bar')
+
+ find('.js-ci-variable-list-section .js-secret-value-reveal-button').click
+ first('.js-ci-variable-input-key').set('foo')
+ first('.js-ci-variable-input-value').set('bar')
click_button 'Save pipeline schedule'
end
scenario 'user sees the updated variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- page.within('.pipeline-variable-list') do
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('foo')
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('bar')
+ page.within('.ci-variable-list') do
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('foo')
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('bar')
end
end
end
@@ -207,15 +209,15 @@ feature 'Pipeline Schedules', :js do
visit_pipelines_schedules
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- find('.pipeline-variable-list .pipeline-variable-row-remove-button').click
+ find('.ci-variable-list .ci-variable-row-remove-button').click
click_button 'Save pipeline schedule'
end
scenario 'user does not see the removed variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- page.within('.pipeline-variable-list') do
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('')
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('')
+ page.within('.ci-variable-list') do
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('')
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('')
end
end
end
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
new file mode 100644
index 00000000000..0170ab458d4
--- /dev/null
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -0,0 +1,163 @@
+import VariableList from '~/ci_variable_list/ci_variable_list';
+import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
+
+describe('VariableList', () => {
+ preloadFixtures('pipeline_schedules/edit.html.raw');
+ preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+
+ let $wrapper;
+ let variableList;
+
+ describe('with only key/value inputs', () => {
+ describe('with no variables', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'schedule',
+ });
+ variableList.init();
+ });
+
+ it('should remove the row when clicking the remove button', () => {
+ $wrapper.find('.js-row-remove-button').trigger('click');
+
+ expect($wrapper.find('.js-row').length).toBe(0);
+ });
+
+ it('should add another row when editing the last rows key input', () => {
+ const $row = $wrapper.find('.js-row');
+ $row.find('.js-ci-variable-input-key')
+ .val('foo')
+ .trigger('input');
+
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ // Check for the correct default in the new row
+ const $keyInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-key');
+ expect($keyInput.val()).toBe('');
+ });
+
+ it('should add another row when editing the last rows value textarea', () => {
+ const $row = $wrapper.find('.js-row');
+ $row.find('.js-ci-variable-input-value')
+ .val('foo')
+ .trigger('input');
+
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ // Check for the correct default in the new row
+ const $valueInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-key');
+ expect($valueInput.val()).toBe('');
+ });
+
+ it('should remove empty row after blurring', () => {
+ const $row = $wrapper.find('.js-row');
+ $row.find('.js-ci-variable-input-key')
+ .val('foo')
+ .trigger('input');
+
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ $row.find('.js-ci-variable-input-key')
+ .val('')
+ .trigger('input')
+ .trigger('blur');
+
+ expect($wrapper.find('.js-row').length).toBe(1);
+ });
+ });
+
+ describe('with persisted variables', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'schedule',
+ });
+ variableList.init();
+ });
+
+ it('should have "Reveal values" button initially when there are already variables', () => {
+ expect($wrapper.find('.js-secret-value-reveal-button').text()).toBe('Reveal values');
+ });
+
+ it('should reveal hidden values', () => {
+ const $row = $wrapper.find('.js-row:first-child');
+ const $inputValue = $row.find('.js-ci-variable-input-value');
+ const $placeholder = $row.find('.js-secret-value-placeholder');
+
+ expect($placeholder.hasClass('hide')).toBe(false);
+ expect($inputValue.hasClass('hide')).toBe(true);
+
+ // Reveal values
+ $wrapper.find('.js-secret-value-reveal-button').click();
+
+ expect($placeholder.hasClass('hide')).toBe(true);
+ expect($inputValue.hasClass('hide')).toBe(false);
+ });
+ });
+ });
+
+ describe('with all inputs(key, value, protected)', () => {
+ beforeEach(() => {
+ // This markup will be replaced with a fixture when we can render the
+ // CI/CD settings page with the new dynamic variable list in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4110
+ $wrapper = $(``);
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'variables',
+ });
+ variableList.init();
+ });
+
+ it('should add another row when editing the last rows protected checkbox', (done) => {
+ const $row = $wrapper.find('.js-row:last-child');
+ $row.find('.ci-variable-protected-item .js-project-feature-toggle').click();
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ // Check for the correct default in the new row
+ const $protectedInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-protected');
+ expect($protectedInput.val()).toBe('true');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
new file mode 100644
index 00000000000..eb508a7f059
--- /dev/null
+++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
@@ -0,0 +1,30 @@
+import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
+
+describe('NativeFormVariableList', () => {
+ preloadFixtures('pipeline_schedules/edit.html.raw');
+
+ let $wrapper;
+
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ setupNativeFormVariableList({
+ container: $wrapper,
+ formField: 'schedule',
+ });
+ });
+
+ describe('onFormSubmit', () => {
+ it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
+ const $row = $wrapper.find('.js-row');
+ expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('schedule[variables_attributes][][key]');
+ expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('schedule[variables_attributes][][value]');
+
+ $wrapper.closest('form').trigger('trigger-submit');
+
+ expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('');
+ expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('');
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb
new file mode 100644
index 00000000000..56f27ea7df1
--- /dev/null
+++ b/spec/javascripts/fixtures/pipeline_schedules.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :public, :repository) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: admin) }
+ let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: admin) }
+ let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
+ let!(:pipeline_schedule_variable2) { create(:ci_pipeline_schedule_variable, key: 'bar', value: 'barvalue', pipeline_schedule: pipeline_schedule_populated) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('pipeline_schedules/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'pipeline_schedules/edit.html.raw' do |example|
+ get :edit,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule.id
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+
+ it 'pipeline_schedules/edit_with_variables.html.raw' do |example|
+ get :edit,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule_populated.id
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js b/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
deleted file mode 100644
index 5b316b319a5..00000000000
--- a/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import {
- setupPipelineVariableList,
- insertRow,
- removeRow,
-} from '~/pipeline_schedules/setup_pipeline_variable_list';
-
-describe('Pipeline Variable List', () => {
- let $markup;
-
- describe('insertRow', () => {
- it('should insert another row', () => {
- $markup = $(`
-
-
-
-
-
`);
-
- insertRow($markup.find('.js-row'));
-
- expect($markup.find('.js-row').length).toBe(2);
- });
-
- it('should clear `data-is-persisted` on cloned row', () => {
- $markup = $(`
-
-
`);
-
- insertRow($markup.find('.js-row'));
-
- const $lastRow = $markup.find('.js-row').last();
- expect($lastRow.attr('data-is-persisted')).toBe(undefined);
- });
-
- it('should clear inputs on cloned row', () => {
- $markup = $(`
-
-
- bar
-
- `);
-
- insertRow($markup.find('.js-row'));
-
- const $lastRow = $markup.find('.js-row').last();
- expect($lastRow.find('input').val()).toBe('');
- expect($lastRow.find('textarea').val()).toBe('');
- });
- });
-
- describe('removeRow', () => {
- it('should remove dynamic row', () => {
- $markup = $(`
-
-
-
-
- `);
-
- removeRow($markup.find('.js-row'));
-
- expect($markup.find('.js-row').length).toBe(0);
- });
-
- it('should hide and mark to destroy with already persisted rows', () => {
- $markup = $(`
-
-
-
- `);
-
- const $row = $markup.find('.js-row');
- removeRow($row);
-
- expect($row.find('.js-destroy-input').val()).toBe('1');
- expect($markup.find('.js-row').length).toBe(1);
- });
- });
-
- describe('setupPipelineVariableList', () => {
- beforeEach(() => {
- $markup = $(`
-
-
-
-
-
-
- `);
-
- setupPipelineVariableList($markup);
- });
-
- it('should remove the row when clicking the remove button', () => {
- $markup.find('.js-row-remove-button').trigger('click');
-
- expect($markup.find('.js-row').length).toBe(0);
- });
-
- it('should add another row when editing the last rows key input', () => {
- const $row = $markup.find('.js-row');
- $row.find('input.js-user-input')
- .val('foo')
- .trigger('input');
-
- expect($markup.find('.js-row').length).toBe(2);
- });
-
- it('should add another row when editing the last rows value textarea', () => {
- const $row = $markup.find('.js-row');
- $row.find('textarea.js-user-input')
- .val('foo')
- .trigger('input');
-
- expect($markup.find('.js-row').length).toBe(2);
- });
-
- it('should remove empty row after blurring', () => {
- const $row = $markup.find('.js-row');
- $row.find('input.js-user-input')
- .val('foo')
- .trigger('input');
-
- expect($markup.find('.js-row').length).toBe(2);
-
- $row.find('input.js-user-input')
- .val('')
- .trigger('input')
- .trigger('blur');
-
- expect($markup.find('.js-row').length).toBe(1);
- });
-
- it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
- const $row = $markup.find('.js-row');
- expect($row.find('input').attr('name')).toBe('schedule[variables_attributes][][key]');
- expect($row.find('textarea').attr('name')).toBe('schedule[variables_attributes][][value]');
-
- $markup.filter('form').submit();
-
- expect($row.find('input').attr('name')).toBe('');
- expect($row.find('textarea').attr('name')).toBe('');
- });
- });
-});
--
cgit v1.2.1
From 73582d9fe786364d585768e1766a29b342b34b33 Mon Sep 17 00:00:00 2001
From: James Ramsay
Date: Wed, 31 Jan 2018 14:32:35 -0500
Subject: Fix subgroup creation docs
---
.../group/subgroups/img/create_subgroup_button.png | Bin 8402 -> 11161 bytes
doc/user/group/subgroups/index.md | 3 ++-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/doc/user/group/subgroups/img/create_subgroup_button.png b/doc/user/group/subgroups/img/create_subgroup_button.png
index 000b54c2855..d1355d4b2c3 100644
Binary files a/doc/user/group/subgroups/img/create_subgroup_button.png and b/doc/user/group/subgroups/img/create_subgroup_button.png differ
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 161a3af9903..2a982344e5f 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -90,7 +90,8 @@ structure.
To create a subgroup:
-1. In the group's dashboard go to the **Subgroups** page and click **New subgroup**.
+1. In the group's dashboard expand the **New project** split button, select
+ **New subgroup** and click the **New subgroup** button.
![Subgroups page](img/create_subgroup_button.png)
--
cgit v1.2.1
From 10a2d95a7b9627a47a338d68c24eb936bc9da01b Mon Sep 17 00:00:00 2001
From: Clement Ho
Date: Wed, 31 Jan 2018 21:53:40 +0000
Subject: Remove namespaced internationalization import
---
app/assets/javascripts/awards_handler.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index d9341837149..87109a802e5 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this */
import _ from 'underscore';
import Cookies from 'js-cookie';
-import { s__ } from './locale';
+import { __ } from './locale';
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
@@ -451,7 +451,7 @@ class AwardsHandler {
callback();
}
})
- .catch(() => flash(s__('Something went wrong on our end.')));
+ .catch(() => flash(__('Something went wrong on our end.')));
}
}
--
cgit v1.2.1
From 9709b5ed1471f70186a95812e2fc0a8537f5eaa3 Mon Sep 17 00:00:00 2001
From: Clement Ho
Date: Wed, 31 Jan 2018 15:56:26 -0600
Subject: Replace $.ajax in activity calendar with axios
---
app/assets/javascripts/users/activity_calendar.js | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js
index 0ca54faa71c..57306322aa4 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/users/activity_calendar.js
@@ -1,7 +1,10 @@
import _ from 'underscore';
import { scaleLinear, scaleThreshold } from 'd3-scale';
import { select } from 'd3-selection';
-import { getDayName, getDayDifference } from '../lib/utils/datetime_utility';
+import { getDayName, getDayDifference } from '~/lib/utils/datetime_utility';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
const d3 = { select, scaleLinear, scaleThreshold };
@@ -221,14 +224,16 @@ export default class ActivityCalendar {
this.currentSelectedDate.getDate(),
].join('-');
- $.ajax({
- url: this.calendarActivitiesPath,
- data: { date },
- cache: false,
- dataType: 'html',
- beforeSend: () => $('.user-calendar-activities').html(LOADING_HTML),
- success: data => $('.user-calendar-activities').html(data),
- });
+ $('.user-calendar-activities').html(LOADING_HTML);
+
+ axios.get(this.calendarActivitiesPath, {
+ params: {
+ date,
+ },
+ responseType: 'text',
+ })
+ .then(({ data }) => $('.user-calendar-activities').html(data))
+ .catch(() => flash(__('An error occurred while retrieving calendar activity')));
} else {
this.currentSelectedDate = '';
$('.user-calendar-activities').html('');
--
cgit v1.2.1
From b8c43a73d3be399271d499800f3ee8e179738788 Mon Sep 17 00:00:00 2001
From: Clement Ho
Date: Wed, 31 Jan 2018 16:27:03 -0600
Subject: Replace $.ajax in find file with axios
---
app/assets/javascripts/project_find_file.js | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 0da32b4a3cc..586d188350f 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -1,6 +1,9 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */
import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
// highlight text(awefwbwgtc -> a wefwb wgtc )
const highlighter = function(element, text, matches) {
@@ -72,19 +75,14 @@ export default class ProjectFindFile {
// files pathes load
load(url) {
- return $.ajax({
- url: url,
- method: "get",
- dataType: "json",
- success: (function(_this) {
- return function(data) {
- _this.element.find(".loading").hide();
- _this.filePaths = data;
- _this.findFile();
- return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
- };
- })(this)
- });
+ axios.get(url)
+ .then(({ data }) => {
+ this.element.find('.loading').hide();
+ this.filePaths = data;
+ this.findFile();
+ this.element.find('.files-slider tr.tree-item').eq(0).addClass('selected').focus();
+ })
+ .catch(() => flash(__('An error occurred while loading filenames')));
}
// render result
--
cgit v1.2.1
From 9291048fc0b4afe228ccf3b59b8260f6ddd09005 Mon Sep 17 00:00:00 2001
From: Stan Hu
Date: Wed, 31 Jan 2018 20:20:38 -0800
Subject: Remove grpc and google-protobuf platform-specific version markers in
Gemfile.lock
Closes gitlab-org/omnibus-gitlab#3128
---
Gemfile.lock | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index f770a7019e7..2ddf8221a06 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -340,7 +340,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-protobuf (3.5.1.1-universal-darwin)
+ google-protobuf (3.5.1.1)
googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0)
googleauth (0.5.3)
@@ -369,7 +369,7 @@ GEM
rake
grape_logging (1.7.0)
grape
- grpc (1.8.3-universal-darwin)
+ grpc (1.8.3)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
googleauth (>= 0.5.1, < 0.7)
--
cgit v1.2.1
From 7f0ebeff1affcd4f5155790cc5a5884b052695af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jarka=20Kadlecova=CC=81?=
Date: Wed, 24 Jan 2018 07:06:24 +0100
Subject: Include subgroup issuables on the group page
---
app/controllers/concerns/issuable_collections.rb | 1 +
app/finders/group_projects_finder.rb | 11 +++-
app/finders/issuable_finder.rb | 4 +-
changelogs/unreleased/30106-group-issues.yml | 5 ++
spec/features/groups/issues_spec.rb | 69 +++++++++++++--------
spec/finders/group_projects_finder_spec.rb | 76 ++++++++++++++++++++++--
spec/finders/issues_finder_spec.rb | 44 +++++++++++---
spec/finders/merge_requests_finder_spec.rb | 41 +++++++++----
8 files changed, 200 insertions(+), 51 deletions(-)
create mode 100644 changelogs/unreleased/30106-group-issues.yml
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index b25e753a5ad..755e324a53f 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -84,6 +84,7 @@ module IssuableCollections
@filter_params[:project_id] = @project.id
elsif @group
@filter_params[:group_id] = @group.id
+ @filter_params[:include_subgroups] = true
else
# TODO: this filter ignore issues/mr created in public or
# internal repos where you are not a member. Enable this filter
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index f2d3b90b8e2..f73cf8adb4d 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -87,8 +87,17 @@ class GroupProjectsFinder < ProjectsFinder
options.fetch(:only_shared, false)
end
+ # subgroups are supported only for owned projects not for shared
+ def include_subgroups?
+ options.fetch(:include_subgroups, false)
+ end
+
def owned_projects
- group.projects
+ if include_subgroups?
+ Project.where(namespace_id: group.self_and_descendants.select(:id))
+ else
+ group.projects
+ end
end
def shared_projects
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 493e7985d75..0fe3000ca01 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -43,6 +43,7 @@ class IssuableFinder
search
sort
state
+ include_subgroups
].freeze
ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze
@@ -148,7 +149,8 @@ class IssuableFinder
if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects
elsif group
- GroupProjectsFinder.new(group: group, current_user: current_user).execute
+ finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
+ GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
else
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
end
diff --git a/changelogs/unreleased/30106-group-issues.yml b/changelogs/unreleased/30106-group-issues.yml
new file mode 100644
index 00000000000..d24996e6087
--- /dev/null
+++ b/changelogs/unreleased/30106-group-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Include subgroup issues and merge requests on the group page
+merge_request:
+author:
+type: changed
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index cdf7aceb13c..450bc0ff8cf 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -3,40 +3,61 @@ require 'spec_helper'
feature 'Group issues page' do
include FilteredSearchHelpers
- let(:path) { issues_group_path(group) }
- let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
+ context 'with shared examples' do
+ let(:path) { issues_group_path(group) }
+ let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
- include_examples 'project features apply to issuables', Issue
+ include_examples 'project features apply to issuables', Issue
- context 'rss feed' do
- let(:access_level) { ProjectFeature::ENABLED }
+ context 'rss feed' do
+ let(:access_level) { ProjectFeature::ENABLED }
- context 'when signed in' do
- let(:user) { user_in_group }
+ context 'when signed in' do
+ let(:user) { user_in_group }
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
- end
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ end
- context 'when signed out' do
- let(:user) { nil }
+ context 'when signed out' do
+ let(:user) { nil }
- it_behaves_like "it has an RSS button without an RSS token"
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ end
end
- end
- context 'assignee', :js do
- let(:access_level) { ProjectFeature::ENABLED }
- let(:user) { user_in_group }
- let(:user2) { user_outside_group }
- let(:path) { issues_group_path(group) }
+ context 'assignee', :js do
+ let(:access_level) { ProjectFeature::ENABLED }
+ let(:user) { user_in_group }
+ let(:user2) { user_outside_group }
+ let(:path) { issues_group_path(group) }
+
+ it 'filters by only group users' do
+ filtered_search.set('assignee:')
- it 'filters by only group users' do
- filtered_search.set('assignee:')
+ expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name)
+ expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name)
+ end
+ end
+ end
- expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name)
- expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name)
+ context 'issues list', :nested_groups do
+ let(:group) { create(:group)}
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project) { create(:project, :public, group: group)}
+ let(:subgroup_project) { create(:project, :public, group: subgroup)}
+ let!(:issue) { create(:issue, project: project, title: 'root group issue') }
+ let!(:subgroup_issue) { create(:issue, project: subgroup_project, title: 'subgroup issue') }
+
+ it 'returns all group and subgroup issues' do
+ visit issues_group_path(group)
+
+ page.within('.issuable-list') do
+ expect(page).to have_selector('li.issue', count: 2)
+ expect(page).to have_content('root group issue')
+ expect(page).to have_content('subgroup issue')
+ end
end
end
end
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index 27a09d7c6f5..be80ee7d767 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe GroupProjectsFinder do
let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
let(:current_user) { create(:user) }
let(:options) { {} }
@@ -12,6 +13,8 @@ describe GroupProjectsFinder do
let!(:shared_project_1) { create(:project, :public, path: '3') }
let!(:shared_project_2) { create(:project, :private, path: '4') }
let!(:shared_project_3) { create(:project, :internal, path: '5') }
+ let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
+ let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
before do
shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
@@ -35,11 +38,31 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- it { is_expected.to match_array([private_project, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([private_project, public_project]) }
+ end
end
context "all" do
- it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project, subgroup_project, subgroup_private_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ end
end
end
@@ -71,9 +94,20 @@ describe GroupProjectsFinder do
context "without external user" do
before do
private_project.add_master(current_user)
+ subgroup_private_project.add_master(current_user)
end
- it { is_expected.to match_array([private_project, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([private_project, public_project]) }
+ end
end
context "with external user" do
@@ -81,12 +115,32 @@ describe GroupProjectsFinder do
current_user.update_attributes(external: true)
end
- it { is_expected.to eq([public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([public_project, subgroup_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to eq([public_project]) }
+ end
end
end
context "all" do
- it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project, subgroup_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) }
+ end
end
end
@@ -100,7 +154,17 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- it { is_expected.to eq([public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([public_project, subgroup_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to eq([public_project]) }
+ end
end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 47fd98234f9..abb7631d7d7 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -3,13 +3,17 @@ require 'spec_helper'
describe IssuesFinder do
set(:user) { create(:user) }
set(:user2) { create(:user) }
- set(:project1) { create(:project) }
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project1) { create(:project, group: group) }
set(:project2) { create(:project) }
+ set(:project3) { create(:project, group: subgroup) }
set(:milestone) { create(:milestone, project: project1) }
set(:label) { create(:label, project: project2) }
set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
+ set(:issue4) { create(:issue, project: project3) }
set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
@@ -25,10 +29,12 @@ describe IssuesFinder do
project1.add_master(user)
project2.add_developer(user)
project2.add_developer(user2)
+ project3.add_developer(user)
issue1
issue2
issue3
+ issue4
award_emoji1
award_emoji2
@@ -39,7 +45,7 @@ describe IssuesFinder do
let(:scope) { 'all' }
it 'returns all issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
context 'filtering by assignee ID' do
@@ -50,6 +56,26 @@ describe IssuesFinder do
end
end
+ context 'filtering by group_id' do
+ let(:params) { { group_id: group.id } }
+
+ context 'when include_subgroup param not set' do
+ it 'returns all group issues' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+
+ context 'when include_subgroup param is true', :nested_groups do
+ before do
+ params[:include_subgroups] = true
+ end
+
+ it 'returns all group and subgroup issues' do
+ expect(issues).to contain_exactly(issue1, issue4)
+ end
+ end
+ end
+
context 'filtering by author ID' do
let(:params) { { author_id: user2.id } }
@@ -87,7 +113,7 @@ describe IssuesFinder do
let(:params) { { milestone_title: Milestone::None.title } }
it 'returns issues with no milestone' do
- expect(issues).to contain_exactly(issue2, issue3)
+ expect(issues).to contain_exactly(issue2, issue3, issue4)
end
end
@@ -185,7 +211,7 @@ describe IssuesFinder do
let(:params) { { label_name: Label::None.title } }
it 'returns issues with no labels' do
- expect(issues).to contain_exactly(issue1, issue3)
+ expect(issues).to contain_exactly(issue1, issue3, issue4)
end
end
@@ -210,7 +236,7 @@ describe IssuesFinder do
let(:params) { { state: 'opened' } }
it 'returns only opened issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
end
@@ -226,7 +252,7 @@ describe IssuesFinder do
let(:params) { { state: 'all' } }
it 'returns all issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
end
end
@@ -234,7 +260,7 @@ describe IssuesFinder do
let(:params) { { state: 'invalid_state' } }
it 'returns all issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
end
end
end
@@ -338,7 +364,7 @@ describe IssuesFinder do
end
it "doesn't return issues if feature disabled" do
- [project1, project2].each do |project|
+ [project1, project2, project3].each do |project|
project.project_feature.update!(issues_access_level: ProjectFeature::DISABLED)
end
@@ -351,7 +377,7 @@ describe IssuesFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
- expect(finder.row_count).to eq(3)
+ expect(finder.row_count).to eq(4)
end
it 'returns the number of rows for a given state' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 687ffaec7cc..9385c892c9e 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -6,31 +6,36 @@ describe MergeRequestsFinder do
let(:user) { create :user }
let(:user2) { create :user }
- let(:project1) { create(:project, :public) }
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project1) { create(:project, :public, group: group) }
let(:project2) { fork_project(project1, user) }
let(:project3) do
p = fork_project(project1, user)
p.update!(archived: true)
p
end
+ let(:project4) { create(:project, :public, group: subgroup) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) }
let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3) }
+ let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4) }
before do
project1.add_master(user)
project2.add_developer(user)
project3.add_developer(user)
project2.add_developer(user2)
+ project4.add_developer(user)
end
describe "#execute" do
it 'filters by scope' do
params = { scope: 'authored', state: 'opened' }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
+ expect(merge_requests.size).to eq(4)
end
it 'filters by project' do
@@ -39,10 +44,26 @@ describe MergeRequestsFinder do
expect(merge_requests.size).to eq(1)
end
+ it 'filters by group' do
+ params = { group_id: group.id }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests.size).to eq(2)
+ end
+
+ it 'filters by group including subgroups', :nested_groups do
+ params = { group_id: group.id, include_subgroups: true }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests.size).to eq(3)
+ end
+
it 'filters by non_archived' do
params = { non_archived: true }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
+ expect(merge_requests.size).to eq(4)
end
it 'filters by iid' do
@@ -73,14 +94,14 @@ describe MergeRequestsFinder do
end
context 'with created_after and created_before params' do
- let(:project4) { create(:project, forked_from_project: project1) }
+ let(:new_project) { create(:project, forked_from_project: project1) }
let!(:new_merge_request) do
create(:merge_request,
:simple,
author: user,
created_at: 1.week.from_now,
- source_project: project4,
+ source_project: new_project,
target_project: project1)
end
@@ -89,12 +110,12 @@ describe MergeRequestsFinder do
:simple,
author: user,
created_at: 1.week.ago,
- source_project: project4,
- target_project: project4)
+ source_project: new_project,
+ target_project: new_project)
end
before do
- project4.add_master(user)
+ new_project.add_master(user)
end
it 'filters by created_after' do
@@ -106,7 +127,7 @@ describe MergeRequestsFinder do
end
it 'filters by created_before' do
- params = { project_id: project4.id, created_before: old_merge_request.created_at + 1.second }
+ params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second }
merge_requests = described_class.new(user, params).execute
@@ -119,7 +140,7 @@ describe MergeRequestsFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
- expect(finder.row_count).to eq(3)
+ expect(finder.row_count).to eq(4)
end
it 'returns the number of rows for a given state' do
--
cgit v1.2.1
From 988747df49d9441f539c51efd6f808f77173c3e0 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 09:07:16 +0000
Subject: fixed notes_spec.js
---
spec/javascripts/notes_spec.js | 182 ++++++++++++++++++++++++++++-------------
1 file changed, 124 insertions(+), 58 deletions(-)
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index a40821a5693..808e0f6ca31 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,11 +1,14 @@
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
import _ from 'underscore';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
import '~/render_gfm';
import Notes from '~/notes';
+import timeoutPromise from './helpers/set_timeout_promise_helper';
(function() {
window.gon || (window.gon = {});
@@ -119,6 +122,7 @@ import Notes from '~/notes';
let noteEntity;
let $form;
let $notesContainer;
+ let mock;
beforeEach(() => {
this.notes = new Notes('', []);
@@ -136,24 +140,28 @@ import Notes from '~/notes';
$form = $('form.js-main-target-form');
$notesContainer = $('ul.main-notes-list');
$form.find('textarea.js-note-text').val(sampleComment);
+
+ mock = new MockAdapter(axios);
+ mock.onPost(/(.*)\/notes$/).reply(200, noteEntity);
});
- it('updates note and resets edit form', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('updates note and resets edit form', (done) => {
spyOn(this.notes, 'revertNoteEditForm');
spyOn(this.notes, 'setupNewNote');
$('.js-comment-button').click();
- deferred.resolve(noteEntity);
- const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
- const updatedNote = Object.assign({}, noteEntity);
- updatedNote.note = 'bar';
- this.notes.updateNote(updatedNote, $targetNote);
+ setTimeout(() => {
+ const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
+ const updatedNote = Object.assign({}, noteEntity);
+ updatedNote.note = 'bar';
+ this.notes.updateNote(updatedNote, $targetNote);
+
+ expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
+ expect(this.notes.setupNewNote).toHaveBeenCalled();
- expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
- expect(this.notes.setupNewNote).toHaveBeenCalled();
+ done();
+ });
});
});
@@ -479,8 +487,19 @@ import Notes from '~/notes';
};
let $form;
let $notesContainer;
+ let mock;
+
+ function mockNotesPost() {
+ mock.onPost(/(.*)\/notes$/).reply(200, note);
+ }
+
+ function mockNotesPostError() {
+ mock.onPost(/(.*)\/notes$/).networkError();
+ }
beforeEach(() => {
+ mock = new MockAdapter(axios);
+
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
@@ -489,63 +508,92 @@ import Notes from '~/notes';
$form.find('textarea.js-note-text').val(sampleComment);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('should show placeholder note while new comment is being posted', () => {
+ mockNotesPost();
+
$('.js-comment-button').click();
expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true);
});
- it('should remove placeholder note when new comment is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should remove placeholder note when new comment is done posting', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- expect($notesContainer.find('.note.being-posted').length).toEqual(0);
+ setTimeout(() => {
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0);
+
+ done();
+ });
});
- it('should show actual note element when new comment is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should show actual note element when new comment is done posting', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
+ setTimeout(() => {
+ expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
+
+ done();
+ });
});
- it('should reset Form when new comment is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should reset Form when new comment is done posting', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- expect($form.find('textarea.js-note-text').val()).toEqual('');
+ setTimeout(() => {
+ expect($form.find('textarea.js-note-text').val()).toEqual('');
+
+ done();
+ });
});
- it('should show flash error message when new comment failed to be posted', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should show flash error message when new comment failed to be posted', (done) => {
+ mockNotesPostError();
+
$('.js-comment-button').click();
- deferred.reject();
- expect($notesContainer.parent().find('.flash-container .flash-text').is(':visible')).toEqual(true);
+ setTimeout(() => {
+ expect($notesContainer.parent().find('.flash-container .flash-text').is(':visible')).toEqual(true);
+
+ done();
+ });
});
- it('should show flash error message when comment failed to be updated', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should show flash error message when comment failed to be updated', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').val(updatedComment);
- $noteEl.find('.js-comment-save-button').click();
+ timeoutPromise()
+ .then(() => {
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').val(updatedComment);
- deferred.reject();
- const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
- expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
- expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(sampleComment); // See if comment reverted back to original
- expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
+ mock.restore();
+
+ mockNotesPostError();
+
+ $noteEl.find('.js-comment-save-button').click();
+ })
+ .then(timeoutPromise)
+ .then(() => {
+ const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
+ expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
+ expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(sampleComment); // See if comment reverted back to original
+ expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
+
+ done();
+ })
+ .catch(done.fail);
});
});
@@ -563,8 +611,12 @@ import Notes from '~/notes';
};
let $form;
let $notesContainer;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onPost(/(.*)\/notes$/).reply(200, note);
+
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
@@ -582,15 +634,20 @@ import Notes from '~/notes';
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should remove slash command placeholder when comment with slash commands is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should remove slash command placeholder when comment with slash commands is done posting', (done) => {
spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
$('.js-comment-button').click();
expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
- deferred.resolve(note);
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+
+ setTimeout(() => {
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+ done();
+ });
});
});
@@ -607,8 +664,12 @@ import Notes from '~/notes';
};
let $form;
let $notesContainer;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onPost(/(.*)\/notes$/).reply(200, note);
+
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
@@ -617,19 +678,24 @@ import Notes from '~/notes';
$form.find('textarea.js-note-text').html(sampleComment);
});
- it('should not render a script tag', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should not render a script tag', (done) => {
$('.js-comment-button').click();
- deferred.resolve(note);
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').html(updatedComment);
- $noteEl.find('.js-comment-save-button').click();
+ setTimeout(() => {
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').html(updatedComment);
+ $noteEl.find('.js-comment-save-button').click();
+
+ const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container');
+ expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
- const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container');
- expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
+ done();
+ });
});
});
--
cgit v1.2.1
From 4377b4795fedcb5145d76fab09ccd468ce3a2138 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 09:14:53 +0000
Subject: remove useless ajaxPost method
---
app/assets/javascripts/lib/utils/common_utils.js | 3 ---
app/assets/javascripts/notes.js | 11 ++++++-----
spec/javascripts/lib/utils/common_utils_spec.js | 13 -------------
3 files changed, 6 insertions(+), 21 deletions(-)
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index c27c4c6e621..5811d059e0b 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -35,8 +35,6 @@ export const ajaxGet = url => axios.get(url, {
$.globalEval(data);
});
-export const ajaxPost = (url, data) => axios.post(url, data);
-
export const rstrip = (val) => {
if (val) {
return val.replace(/\s+$/, '');
@@ -409,7 +407,6 @@ window.gl.utils = {
getGroupSlug,
isInIssuePage,
ajaxGet,
- ajaxPost,
rstrip,
updateTooltipTitle,
disableButtonIfEmptyField,
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 2f37d40ebad..429d50f5463 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -17,13 +17,14 @@ import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import { getLocationHash } from './lib/utils/url_utility';
+import axios from './lib/utils/axios_utils';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler';
import Autosave from './autosave';
import TaskList from './task_list';
-import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
+import { isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
@@ -1404,7 +1405,7 @@ export default class Notes {
* 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
* 3) Build temporary placeholder element (using `createPlaceholderNote`)
* 4) Show placeholder note on UI
- * 5) Perform network request to submit the note using `ajaxPost`
+ * 5) Perform network request to submit the note using `axios.post`
* a) If request is successfully completed
* 1. Remove placeholder element
* 2. Show submitted Note element
@@ -1486,7 +1487,7 @@ export default class Notes {
/* eslint-disable promise/catch-or-return */
// Make request to submit comment on server
- ajaxPost(formAction, formData)
+ axios.post(formAction, formData)
.then((res) => {
const note = res.data;
@@ -1601,7 +1602,7 @@ export default class Notes {
*
* 1) Get Form metadata
* 2) Update note element with new content
- * 3) Perform network request to submit the updated note using `ajaxPost`
+ * 3) Perform network request to submit the updated note using `axios.post`
* a) If request is successfully completed
* 1. Show submitted Note element
* b) If request failed
@@ -1632,7 +1633,7 @@ export default class Notes {
/* eslint-disable promise/catch-or-return */
// Make request to update comment on server
- ajaxPost(formAction, formData)
+ axios.post(formAction, formData)
.then(({ data }) => {
// Submission successful! render final note element
this.updateNote(data, $editingNote);
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 864ee4995ea..80430011aed 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -459,19 +459,6 @@ describe('common_utils', () => {
});
});
- describe('ajaxPost', () => {
- it('should perform `$.ajax` call and do `POST` request', () => {
- const requestURL = '/some/random/api';
- const data = { keyname: 'value' };
- const ajaxSpy = spyOn(axios, 'post').and.callFake(() => {});
-
- commonUtils.ajaxPost(requestURL, data);
-
- expect(ajaxSpy.calls.allArgs()[0][0]).toEqual(requestURL);
- expect(ajaxSpy.calls.allArgs()[0][1]).toEqual(data);
- });
- });
-
describe('spriteIcon', () => {
let beforeGon;
--
cgit v1.2.1
From b5b304466fbd8904196afeaa65adeb5282b21987 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 10:34:12 +0000
Subject: Converted mini_pipeline_graph_dropdown.js to axios
---
.../javascripts/mini_pipeline_graph_dropdown.js | 26 ++++----
.../mini_pipeline_graph_dropdown_spec.js | 73 ++++++++++++++--------
2 files changed, 58 insertions(+), 41 deletions(-)
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index ca3d271663b..c7bccd483ac 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
-import Flash from './flash';
+import flash from './flash';
+import axios from './lib/utils/axios_utils';
/**
* In each pipelines table we have a mini pipeline graph for each pipeline.
@@ -78,27 +79,22 @@ export default class MiniPipelineGraph {
const button = e.relatedTarget;
const endpoint = button.dataset.stageEndpoint;
- return $.ajax({
- dataType: 'json',
- type: 'GET',
- url: endpoint,
- beforeSend: () => {
- this.renderBuildsList(button, '');
- this.toggleLoading(button);
- },
- success: (data) => {
+ this.renderBuildsList(button, '');
+ this.toggleLoading(button);
+
+ axios.get(endpoint)
+ .then(({ data }) => {
this.toggleLoading(button);
this.renderBuildsList(button, data.html);
this.stopDropdownClickPropagation();
- },
- error: () => {
+ })
+ .catch(() => {
this.toggleLoading(button);
if ($(button).parent().hasClass('open')) {
$(button).dropdown('toggle');
}
- new Flash('An error occurred while fetching the builds.', 'alert');
- },
- });
+ flash('An error occurred while fetching the builds.', 'alert');
+ });
}
/**
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index 481b46c3ac6..6fa6f44f953 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
@@ -1,7 +1,9 @@
/* eslint-disable no-new */
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
-import '~/flash';
+import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Mini Pipeline Graph Dropdown', () => {
preloadFixtures('static/mini_dropdown_graph.html.raw');
@@ -27,6 +29,16 @@ describe('Mini Pipeline Graph Dropdown', () => {
});
describe('When dropdown is clicked', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
it('should call getBuildsList', () => {
const getBuildsListSpy = spyOn(
MiniPipelineGraph.prototype,
@@ -41,46 +53,55 @@ describe('Mini Pipeline Graph Dropdown', () => {
});
it('should make a request to the endpoint provided in the html', () => {
- const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {});
+ const ajaxSpy = spyOn(axios, 'get').and.callThrough();
+
+ mock.onGet('foobar').reply(200, {
+ html: '',
+ });
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
- expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar');
+ expect(ajaxSpy.calls.allArgs()[0][0]).toEqual('foobar');
});
- it('should not close when user uses cmd/ctrl + click', () => {
- spyOn($, 'ajax').and.callFake(function (params) {
- params.success({
- html: `
-
-
- build
-
-
- `,
- });
+ it('should not close when user uses cmd/ctrl + click', (done) => {
+ mock.onGet('foobar').reply(200, {
+ html: `
+
+
+ build
+
+
+ `,
});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
- document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
-
- expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
+ timeoutPromise()
+ .then(() => {
+ document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
+ })
+ .then(timeoutPromise)
+ .then(() => {
+ expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
- });
- it('should close the dropdown when request returns an error', (done) => {
- spyOn($, 'ajax').and.callFake(options => options.error());
+ it('should close the dropdown when request returns an error', (done) => {
+ mock.onGet('foobar').networkError();
- new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
+ new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
- document.querySelector('.js-builds-dropdown-button').click();
+ document.querySelector('.js-builds-dropdown-button').click();
- setTimeout(() => {
- expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
- done();
- }, 0);
+ setTimeout(() => {
+ expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
+ done();
+ });
+ });
});
});
--
cgit v1.2.1
From c09a89c7557a93b728bab9eef9175abb1fd458d4 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 10:42:49 +0000
Subject: Converted branch_graph.js to axios
---
app/assets/javascripts/network/branch_graph.js | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index 5aad3908eb6..d3edcb724f1 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,5 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
+import { __ } from '../locale';
+import axios from '../lib/utils/axios_utils';
+import flash from '../flash';
import Raphael from './raphael';
export default (function() {
@@ -26,16 +29,13 @@ export default (function() {
}
BranchGraph.prototype.load = function() {
- return $.ajax({
- url: this.options.url,
- method: "get",
- dataType: "json",
- success: $.proxy(function(data) {
+ axios.get(this.options.url)
+ .then(({ data }) => {
$(".loading", this.element).hide();
this.prepareData(data.days, data.commits);
- return this.buildGraph();
- }, this)
- });
+ this.buildGraph();
+ })
+ .catch(() => __('Error fetching network graph.'));
};
BranchGraph.prototype.prepareData = function(days, commits) {
--
cgit v1.2.1
From 1c8553f21e1cbfa730f47ea8c0be4080a9238f89 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 10:50:18 +0000
Subject: Converted notes.js to axios
---
app/assets/javascripts/notes.js | 35 +++++++++++++++--------------------
1 file changed, 15 insertions(+), 20 deletions(-)
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index a2b8e6f6495..bcb342f407f 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -16,6 +16,7 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
+import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle';
@@ -252,26 +253,20 @@ export default class Notes {
return;
}
this.refreshing = true;
- return $.ajax({
- url: this.notes_url,
- headers: { 'X-Last-Fetched-At': this.last_fetched_at },
- dataType: 'json',
- success: (function(_this) {
- return function(data) {
- var notes;
- notes = data.notes;
- _this.last_fetched_at = data.last_fetched_at;
- _this.setPollingInterval(data.notes.length);
- return $.each(notes, function(i, note) {
- _this.renderNote(note);
- });
- };
- })(this)
- }).always((function(_this) {
- return function() {
- return _this.refreshing = false;
- };
- })(this));
+ axios.get(this.notes_url, {
+ headers: {
+ 'X-Last-Fetched-At': this.last_fetched_at,
+ },
+ }).then(({ data }) => {
+ const notes = data.notes;
+ this.last_fetched_at = data.last_fetched_at;
+ this.setPollingInterval(data.notes.length);
+ $.each(notes, (i, note) => this.renderNote(note));
+
+ this.refreshing = false;
+ }).catch(() => {
+ this.refreshing = false;
+ });
}
/**
--
cgit v1.2.1
From ab8e3a5595a441eb5b24cd7db5a877b65dcef704 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 10:53:36 +0000
Subject: Converted notifications_form.js to axios
---
app/assets/javascripts/notifications_form.js | 38 ++++++++++++++--------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
index 4534360d577..4e0afe13590 100644
--- a/app/assets/javascripts/notifications_form.js
+++ b/app/assets/javascripts/notifications_form.js
@@ -1,3 +1,7 @@
+import { __ } from './locale';
+import axios from './lib/utils/axios_utils';
+import flash from './flash';
+
export default class NotificationsForm {
constructor() {
this.toggleCheckbox = this.toggleCheckbox.bind(this);
@@ -27,24 +31,20 @@ export default class NotificationsForm {
saveEvent($checkbox, $parent) {
const form = $parent.parents('form:first');
- return $.ajax({
- url: form.attr('action'),
- method: form.attr('method'),
- dataType: 'json',
- data: form.serialize(),
- beforeSend: () => {
- this.showCheckboxLoadingSpinner($parent);
- },
- }).done((data) => {
- $checkbox.enable();
- if (data.saved) {
- $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
- setTimeout(() => {
- $parent.removeClass('is-loading')
- .find('.custom-notification-event-loading')
- .toggleClass('fa-spin fa-spinner fa-check is-done');
- }, 2000);
- }
- });
+ this.showCheckboxLoadingSpinner($parent);
+
+ axios[form.attr('method')](form.attr('action'), form.serialize())
+ .then(({ data }) => {
+ $checkbox.enable();
+ if (data.saved) {
+ $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
+ setTimeout(() => {
+ $parent.removeClass('is-loading')
+ .find('.custom-notification-event-loading')
+ .toggleClass('fa-spin fa-spinner fa-check is-done');
+ }, 2000);
+ }
+ })
+ .catch(() => flash(__('There was an error saving your notification settings.')));
}
}
--
cgit v1.2.1
From ee1c471bad95cb640ea63393954825dd5a68a9e2 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 11:12:37 +0000
Subject: Converted pager.js to axios
---
app/assets/javascripts/pager.js | 33 +++++++++---------
spec/javascripts/pager_spec.js | 76 +++++++++++++++++++++++++++++++++--------
2 files changed, 77 insertions(+), 32 deletions(-)
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index 6552a88b606..fd3105b1960 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -1,4 +1,5 @@
import { getParameterByName } from '~/lib/utils/common_utils';
+import axios from './lib/utils/axios_utils';
import { removeParams } from './lib/utils/url_utility';
const ENDLESS_SCROLL_BOTTOM_PX = 400;
@@ -22,24 +23,22 @@ export default {
getOld() {
this.loading.show();
- $.ajax({
- type: 'GET',
- url: this.url,
- data: `limit=${this.limit}&offset=${this.offset}`,
- dataType: 'json',
- error: () => this.loading.hide(),
- success: (data) => {
- this.append(data.count, this.prepareData(data.html));
- this.callback();
-
- // keep loading until we've filled the viewport height
- if (!this.disable && !this.isScrollable()) {
- this.getOld();
- } else {
- this.loading.hide();
- }
+ axios.get(this.url, {
+ params: {
+ limit: this.limit,
+ offset: this.offset,
},
- });
+ }).then(({ data }) => {
+ this.append(data.count, this.prepareData(data.html));
+ this.callback();
+
+ // keep loading until we've filled the viewport height
+ if (!this.disable && !this.isScrollable()) {
+ this.getOld();
+ } else {
+ this.loading.hide();
+ }
+ }).catch(() => this.loading.hide());
},
append(count, html) {
diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js
index 2fd87754238..fd9b83e3514 100644
--- a/spec/javascripts/pager_spec.js
+++ b/spec/javascripts/pager_spec.js
@@ -1,5 +1,6 @@
/* global fixture */
-
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import * as utils from '~/lib/utils/url_utility';
import Pager from '~/pager';
@@ -9,7 +10,6 @@ describe('pager', () => {
beforeEach(() => {
setFixtures('
');
- spyOn($, 'ajax');
});
afterEach(() => {
@@ -47,39 +47,85 @@ describe('pager', () => {
});
describe('getOld', () => {
+ const urlRegex = /\/some_list(.*)$/;
+ let mock;
+
+ function mockSuccess() {
+ mock.onGet(urlRegex).reply(200, {
+ count: 20,
+ html: '',
+ });
+ }
+
+ function mockError() {
+ mock.onGet(urlRegex).networkError();
+ }
+
beforeEach(() => {
setFixtures('
');
+ spyOn(axios, 'get').and.callThrough();
+
Pager.init();
+
+ mock = new MockAdapter(axios);
});
- it('shows loader while loading next page', () => {
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('shows loader while loading next page', (done) => {
+ mockSuccess();
+
spyOn(Pager.loading, 'show');
Pager.getOld();
- expect(Pager.loading.show).toHaveBeenCalled();
+
+ setTimeout(() => {
+ expect(Pager.loading.show).toHaveBeenCalled();
+
+ done();
+ });
});
- it('hides loader on success', () => {
- spyOn($, 'ajax').and.callFake(options => options.success({}));
+ it('hides loader on success', (done) => {
+ mockSuccess();
+
spyOn(Pager.loading, 'hide');
Pager.getOld();
- expect(Pager.loading.hide).toHaveBeenCalled();
+
+ setTimeout(() => {
+ expect(Pager.loading.hide).toHaveBeenCalled();
+
+ done();
+ });
});
- it('hides loader on error', () => {
- spyOn($, 'ajax').and.callFake(options => options.error());
+ it('hides loader on error', (done) => {
+ mockError();
+
spyOn(Pager.loading, 'hide');
Pager.getOld();
- expect(Pager.loading.hide).toHaveBeenCalled();
+
+ setTimeout(() => {
+ expect(Pager.loading.hide).toHaveBeenCalled();
+
+ done();
+ });
});
- it('sends request to url with offset and limit params', () => {
- spyOn($, 'ajax');
+ it('sends request to url with offset and limit params', (done) => {
Pager.offset = 100;
Pager.limit = 20;
Pager.getOld();
- const [{ data, url }] = $.ajax.calls.argsFor(0);
- expect(data).toBe('limit=20&offset=100');
- expect(url).toBe('/some_list');
+
+ setTimeout(() => {
+ const [url, params] = $.ajax.calls.argsFor(0);
+ console.log(url, params);
+ // expect(data).toBe('limit=20&offset=100');
+ // expect(url).toBe('/some_list');
+
+ done();
+ });
});
});
});
--
cgit v1.2.1
From 57f8b24fe2d54df4dfcc3ddcb5796e41093f0fd7 Mon Sep 17 00:00:00 2001
From: Tony Rom
Date: Thu, 1 Feb 2018 13:35:03 +0300
Subject: Fix minors
---
app/assets/stylesheets/framework/gfm.scss | 16 +++++++---------
lib/banzai/color_parser.rb | 22 ++++++++--------------
spec/javascripts/gfm_auto_complete_spec.js | 8 ++++----
3 files changed, 19 insertions(+), 27 deletions(-)
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 6eff57157cc..e378e84ca1b 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -19,21 +19,19 @@
.gfm-color_chip {
display: inline-block;
- margin-left: 4px;
- margin-bottom: 2px;
+ margin: 0 0 2px 4px;
vertical-align: middle;
border-radius: 3px;
- $side: 0.9em;
- $bg-size: $side / 0.9;
+ $chip-size: 0.9em;
+ $bg-size: $chip-size / 0.9;
$bg-pos: $bg-size / 2;
- $bg-color: $gray-dark;
- width: $side;
- height: $side;
+ width: $chip-size;
+ height: $chip-size;
background: $white-light;
- background-image: linear-gradient(135deg, $bg-color 25%, transparent 0%, transparent 75%, $bg-color 0%),
- linear-gradient(135deg, $bg-color 25%, transparent 0%, transparent 75%, $bg-color 0%);
+ background-image: linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%),
+ linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
background-size: $bg-size $bg-size;
background-position: 0 0, $bg-pos $bg-pos;
diff --git a/lib/banzai/color_parser.rb b/lib/banzai/color_parser.rb
index d96c0a1ed1f..0cd9085190c 100644
--- a/lib/banzai/color_parser.rb
+++ b/lib/banzai/color_parser.rb
@@ -30,21 +30,15 @@ module Banzai
FORMATS = [HEX_FORMAT, RGB_FORMAT, HSL_FORMAT].freeze
- class << self
- # Public: Analyzes whether the String is a color code.
- #
- # text - The String to be parsed.
- #
- # Returns the recognized color String or nil if none was found.
- def parse(text)
- text if color_format =~ text
- end
+ COLOR_FORMAT = /\A(#{Regexp.union(FORMATS)})\z/ix
- private
-
- def color_format
- @color_format ||= /\A(#{Regexp.union(FORMATS)})\z/ix
- end
+ # Public: Analyzes whether the String is a color code.
+ #
+ # text - The String to be parsed.
+ #
+ # Returns the recognized color String or nil if none was found.
+ def self.parse(text)
+ text if COLOR_FORMAT =~ text
end
end
end
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 182880d42e9..50a587ef351 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -130,11 +130,11 @@ describe('GfmAutoComplete', function () {
});
describe('should not match special sequences', () => {
- const ShouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
- const ShouldNotBePrependedBy = ['`'];
+ const shouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
+ const shouldNotBePrependedBy = ['`'];
flagsUseDefaultMatcher.forEach((atSign) => {
- ShouldNotBeFollowedBy.forEach((followedSymbol) => {
+ shouldNotBeFollowedBy.forEach((followedSymbol) => {
const seq = atSign + followedSymbol;
it(`should not match "${seq}"`, () => {
@@ -142,7 +142,7 @@ describe('GfmAutoComplete', function () {
});
});
- ShouldNotBePrependedBy.forEach((prependedSymbol) => {
+ shouldNotBePrependedBy.forEach((prependedSymbol) => {
const seq = prependedSymbol + atSign;
it(`should not match "${seq}"`, () => {
--
cgit v1.2.1
From 43f1088f5b086b95a3d5cdd90a33d26bb483cba5 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 11:18:01 +0000
Subject: Converted usage_ping.js to use axios
also removed dependancy on jQuery because it is super simple & not required
---
.../javascripts/pages/admin/cohorts/usage_ping.js | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js
index 2389056bd02..914a9661c27 100644
--- a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js
+++ b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js
@@ -1,12 +1,13 @@
+import axios from '../../../lib/utils/axios_utils';
+import { __ } from '../../../locale';
+import flash from '../../../flash';
+
export default function UsagePing() {
- const usageDataUrl = $('.usage-data').data('endpoint');
+ const el = document.querySelector('.usage-data');
- $.ajax({
- type: 'GET',
- url: usageDataUrl,
- dataType: 'html',
- success(html) {
- $('.usage-data').html(html);
- },
- });
+ axios.get(el.dataset.endpoint, {
+ responseType: 'text',
+ }).then(({ data }) => {
+ el.innerHTML = data;
+ }).catch(() => flash(__('Error fetching usage ping data.')));
}
--
cgit v1.2.1
From 4cea24f89c757dafcba190b46d1f5f866acd50f6 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 11:32:31 +0000
Subject: Converted todos.js to axios
---
.../pages/dashboard/todos/index/todos.js | 37 +++++++++-------------
1 file changed, 15 insertions(+), 22 deletions(-)
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index e976a3d2f1d..b3f6a72fdcb 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -2,6 +2,9 @@
import { visitUrl } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select';
import { isMetaClick } from '~/lib/utils/common_utils';
+import { __ } from '../../../../locale';
+import flash from '../../../../flash';
+import axios from '../../../../lib/utils/axios_utils';
export default class Todos {
constructor() {
@@ -59,18 +62,12 @@ export default class Todos {
const target = e.target;
target.setAttribute('disabled', true);
target.classList.add('disabled');
- $.ajax({
- type: 'POST',
- url: target.dataset.href,
- dataType: 'json',
- data: {
- '_method': target.dataset.method,
- },
- success: (data) => {
+
+ axios[target.dataset.method](target.dataset.href)
+ .then(({ data }) => {
this.updateRowState(target);
- return this.updateBadges(data);
- },
- });
+ this.updateBadges(data);
+ }).catch(() => flash(__('Error updating todo status.')));
}
updateRowState(target) {
@@ -98,19 +95,15 @@ export default class Todos {
e.preventDefault();
const target = e.currentTarget;
- const requestData = { '_method': target.dataset.method, ids: this.todo_ids };
target.setAttribute('disabled', true);
target.classList.add('disabled');
- $.ajax({
- type: 'POST',
- url: target.dataset.href,
- dataType: 'json',
- data: requestData,
- success: (data) => {
- this.updateAllState(target, data);
- return this.updateBadges(data);
- },
- });
+
+ axios[target.dataset.method](target.dataset.href, {
+ ids: this.todo_ids,
+ }).then(({ data }) => {
+ this.updateAllState(target, data);
+ this.updateBadges(data);
+ }).catch(() => flash(__('Error updating status for all todos.')));
}
updateAllState(target, data) {
--
cgit v1.2.1
From b1c8b3aadb202898a1e9342706e9e6f46bbb3612 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 11:59:59 +0000
Subject: fixed axios mock not being restored
---
spec/javascripts/notes_spec.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 808e0f6ca31..2fb385bd79f 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -145,6 +145,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
mock.onPost(/(.*)\/notes$/).reply(200, noteEntity);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('updates note and resets edit form', (done) => {
spyOn(this.notes, 'revertNoteEditForm');
spyOn(this.notes, 'setupNewNote');
--
cgit v1.2.1
From 0a47d1924d6b283a174672f33cf7a0de6b281fef Mon Sep 17 00:00:00 2001
From: Zeger-Jan van de Weg
Date: Tue, 30 Jan 2018 09:59:45 +0100
Subject: Client changes for Tag,BranchNamesContainingCommit
As part of gitlab-org/gitaly#884, this commit contains the client
implementation for both TagNamesContaintingCommit and
BranchNamesContainingCommit. The interface in the Repository model stays
the same, but the implementation on the serverside, e.g. Gitaly, uses
`for-each-ref`, as opposed to `branch` or `tag` which both aren't
plumbing command. The result stays the same.
On the serverside, we have the opportunity to limit the number of names
to return. However, this is not supported on the front end yet. My
proposal to use this ability: gitlab-org/gitlab-ce#42581. For now, this
ability is not used as that would change more behaviours on a feature
flag which might lead to unexpected changes on page refresh for example.
---
app/models/repository.rb | 4 +--
lib/gitlab/git/branch.rb | 18 ++++++++-----
lib/gitlab/git/repository.rb | 42 +++++++++++++++++++++---------
lib/gitlab/git/tag.rb | 8 ++++--
lib/gitlab/gitaly_client/ref_service.rb | 26 +++++++++++++++++++
spec/models/repository_spec.rb | 45 +++++++++++++++++++++++++--------
6 files changed, 109 insertions(+), 34 deletions(-)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index edfb236a91a..7f443846a82 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -721,11 +721,11 @@ class Repository
end
def branch_names_contains(sha)
- refs_contains_sha('branch', sha)
+ raw_repository.branch_names_contains_sha(sha)
end
def tag_names_contains(sha)
- refs_contains_sha('tag', sha)
+ raw_repository.tag_names_contains_sha(sha)
end
def local_branches
diff --git a/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb
index 3487e099381..2c051bd33b9 100644
--- a/lib/gitlab/git/branch.rb
+++ b/lib/gitlab/git/branch.rb
@@ -1,13 +1,17 @@
-# Gitaly note: JV: no RPC's here.
-
module Gitlab
module Git
class Branch < Ref
- def self.find(repo, branch_name)
- if branch_name.is_a?(Gitlab::Git::Branch)
- branch_name
- else
- repo.find_branch(branch_name)
+ class << self
+ def find(repo, branch_name)
+ if branch_name.is_a?(Gitlab::Git::Branch)
+ branch_name
+ else
+ repo.find_branch(branch_name)
+ end
+ end
+
+ def names_contains_sha(repo, sha, limit: 0)
+ GitalyClient::RefService.new(repo).branch_names_contains_sha(sha)
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 68b54d28876..76b94bf9030 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1355,20 +1355,23 @@ module Gitlab
raise CommandError.new(e)
end
- def refs_contains_sha(ref_type, sha)
- args = %W(#{ref_type} --contains #{sha})
- names = run_git(args).first
-
- if names.respond_to?(:split)
- names = names.split("\n").map(&:strip)
-
- names.each do |name|
- name.slice! '* '
+ def branch_names_contains_sha(sha)
+ gitaly_migrate(:branch_names_contains_sha) do |is_enabled|
+ if is_enabled
+ Gitlab::Git::Branch.names_contains_sha(self, sha)
+ else
+ refs_contains_sha(:branch, sha)
end
+ end
+ end
- names
- else
- []
+ def tag_names_contains_sha(sha)
+ gitaly_migrate(:tag_names_contains_sha) do |is_enabled|
+ if is_enabled
+ Gitlab::Git::Tag.names_contains_sha(self, sha)
+ else
+ refs_contains_sha(:tag, sha)
+ end
end
end
@@ -1446,6 +1449,21 @@ module Gitlab
end
end
+ def refs_contains_sha(ref_type, sha)
+ args = %W(#{ref_type} --contains #{sha})
+ names = run_git(args).first
+
+ return [] unless names.respond_to?(:split)
+
+ names = names.split("\n").map(&:strip)
+
+ names.each do |name|
+ name.slice! '* '
+ end
+
+ names
+ end
+
def shell_write_ref(ref_path, ref, old_ref)
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb
index bc4e160dce9..c163d383eb0 100644
--- a/lib/gitlab/git/tag.rb
+++ b/lib/gitlab/git/tag.rb
@@ -1,8 +1,12 @@
-# Gitaly note: JV: no RPC's here.
-#
module Gitlab
module Git
class Tag < Ref
+ class << self
+ def names_contains_sha(repo, sha)
+ GitalyClient::RefService.new(repo).branch_names_contains_sha(sha)
+ end
+ end
+
attr_reader :object_sha
def initialize(repository, name, target, target_commit, message = nil)
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 8b9a224b700..07122da4c2c 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -145,6 +145,32 @@ module Gitlab
raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
end
+ # Limit: 0 implies no limit, thus all tag names will be returned
+ def tag_names_containing(sha, limit: 0)
+ request = Gitaly::ListTagNamesContainingCommitRequest.new(
+ repository: @gitaly_repo,
+ commit_id: sha,
+ limit: limit
+ )
+
+ stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request)
+
+ stream.each_with_object([]) { |response, array| array.concat(response.tag_names) }
+ end
+
+ # Limit: 0 implies no limit, thus all tag names will be returned
+ def branch_names_contains_sha(sha, limit: 0)
+ request = Gitaly::ListBranchNamesContainingCommitRequest.new(
+ repository: @gitaly_repo,
+ commit_id: sha,
+ limit: limit
+ )
+
+ stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request)
+
+ stream.each_with_object([]) { |response, array| array.concat(response.branch_names) }
+ end
+
private
def consume_refs_response(response)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 1102b1c9006..07d43acd338 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -36,26 +36,49 @@ describe Repository do
end
describe '#branch_names_contains' do
- subject { repository.branch_names_contains(sample_commit.id) }
+ shared_examples '#branch_names_contains' do
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
- it { is_expected.to include('master') }
- it { is_expected.not_to include('feature') }
- it { is_expected.not_to include('fix') }
+ subject { repository.branch_names_contains(sample_commit.id) }
- describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
- expect_to_raise_storage_error do
- broken_repository.branch_names_contains(sample_commit.id)
+ it { is_expected.to include('master') }
+ it { is_expected.not_to include('feature') }
+ it { is_expected.not_to include('fix') }
+
+ describe 'when storage is broken', :broken_storage do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.branch_names_contains(sample_commit.id)
+ end
end
end
end
+
+ context 'when gitaly is enabled' do
+ it_behaves_like '#branch_names_contains'
+ end
+
+ context 'when gitaly is disabled', :skip_gitaly_mock do
+ it_behaves_like '#branch_names_contains'
+ end
end
describe '#tag_names_contains' do
- subject { repository.tag_names_contains(sample_commit.id) }
+ shared_examples '#tag_names_contains' do
+ subject { repository.tag_names_contains(sample_commit.id) }
+
+ it { is_expected.to include('v1.1.0') }
+ it { is_expected.not_to include('v1.0.0') }
+ end
+
+ context 'when gitaly is enabled' do
+ it_behaves_like '#tag_names_contains'
+ end
- it { is_expected.to include('v1.1.0') }
- it { is_expected.not_to include('v1.0.0') }
+ context 'when gitaly is enabled', :skip_gitaly_mock do
+ it_behaves_like '#tag_names_contains'
+ end
end
describe 'tags_sorted_by' do
--
cgit v1.2.1
From 73bd48de977bea1c8cd8bb3d995b984b874d4eba Mon Sep 17 00:00:00 2001
From: Zeger-Jan van de Weg
Date: Tue, 30 Jan 2018 10:56:24 +0100
Subject: Fix encoding issues when name is not UTF-8
---
lib/gitlab/gitaly_client/ref_service.rb | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 07122da4c2c..22fee1ff07f 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -155,7 +155,10 @@ module Gitlab
stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request)
- stream.each_with_object([]) { |response, array| array.concat(response.tag_names) }
+ stream.each_with_object([]) do |response, array|
+ encoded_names = response.tag_names.map { |t| Gitlab::Git.ref_name(t) }
+ array.concat(encoded_names)
+ end
end
# Limit: 0 implies no limit, thus all tag names will be returned
@@ -168,7 +171,10 @@ module Gitlab
stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request)
- stream.each_with_object([]) { |response, array| array.concat(response.branch_names) }
+ stream.each_with_object([]) do |response, array|
+ encoded_names = response.branch_names.map { |b| Gitlab::Git.ref_name(b) }
+ array.concat(encoded_names)
+ end
end
private
--
cgit v1.2.1
From fd46d6ceb81eb9039b4e60c1d158848dd22ba411 Mon Sep 17 00:00:00 2001
From: Zeger-Jan van de Weg
Date: Tue, 30 Jan 2018 11:22:08 +0100
Subject: Remove intermediate methods on Branch and Tag classes
---
lib/gitlab/git/branch.rb | 16 +++++-----------
lib/gitlab/git/repository.rb | 4 ++--
lib/gitlab/git/tag.rb | 6 ------
lib/gitlab/gitaly_client/ref_service.rb | 19 ++++++++++---------
4 files changed, 17 insertions(+), 28 deletions(-)
diff --git a/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb
index 2c051bd33b9..ae7e88f0503 100644
--- a/lib/gitlab/git/branch.rb
+++ b/lib/gitlab/git/branch.rb
@@ -1,17 +1,11 @@
module Gitlab
module Git
class Branch < Ref
- class << self
- def find(repo, branch_name)
- if branch_name.is_a?(Gitlab::Git::Branch)
- branch_name
- else
- repo.find_branch(branch_name)
- end
- end
-
- def names_contains_sha(repo, sha, limit: 0)
- GitalyClient::RefService.new(repo).branch_names_contains_sha(sha)
+ def self.find(repo, branch_name)
+ if branch_name.is_a?(Gitlab::Git::Branch)
+ branch_name
+ else
+ repo.find_branch(branch_name)
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 76b94bf9030..95644dacc4e 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1358,7 +1358,7 @@ module Gitlab
def branch_names_contains_sha(sha)
gitaly_migrate(:branch_names_contains_sha) do |is_enabled|
if is_enabled
- Gitlab::Git::Branch.names_contains_sha(self, sha)
+ gitaly_ref_client.branch_names_contains_sha(sha)
else
refs_contains_sha(:branch, sha)
end
@@ -1368,7 +1368,7 @@ module Gitlab
def tag_names_contains_sha(sha)
gitaly_migrate(:tag_names_contains_sha) do |is_enabled|
if is_enabled
- Gitlab::Git::Tag.names_contains_sha(self, sha)
+ gitaly_ref_client.tag_names_contains_sha(sha)
else
refs_contains_sha(:tag, sha)
end
diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb
index c163d383eb0..8a8f7b051ed 100644
--- a/lib/gitlab/git/tag.rb
+++ b/lib/gitlab/git/tag.rb
@@ -1,12 +1,6 @@
module Gitlab
module Git
class Tag < Ref
- class << self
- def names_contains_sha(repo, sha)
- GitalyClient::RefService.new(repo).branch_names_contains_sha(sha)
- end
- end
-
attr_reader :object_sha
def initialize(repository, name, target, target_commit, message = nil)
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 22fee1ff07f..ba6b577fd17 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -146,7 +146,7 @@ module Gitlab
end
# Limit: 0 implies no limit, thus all tag names will be returned
- def tag_names_containing(sha, limit: 0)
+ def tag_names_contains_sha(sha, limit: 0)
request = Gitaly::ListTagNamesContainingCommitRequest.new(
repository: @gitaly_repo,
commit_id: sha,
@@ -155,10 +155,7 @@ module Gitlab
stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request)
- stream.each_with_object([]) do |response, array|
- encoded_names = response.tag_names.map { |t| Gitlab::Git.ref_name(t) }
- array.concat(encoded_names)
- end
+ consume_ref_contains_sha_response(stream, :tag_names)
end
# Limit: 0 implies no limit, thus all tag names will be returned
@@ -171,10 +168,7 @@ module Gitlab
stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request)
- stream.each_with_object([]) do |response, array|
- encoded_names = response.branch_names.map { |b| Gitlab::Git.ref_name(b) }
- array.concat(encoded_names)
- end
+ consume_ref_contains_sha_response(stream, :branch_names)
end
private
@@ -247,6 +241,13 @@ module Gitlab
Gitlab::Git::Commit.decorate(@repository, hash)
end
+ def consume_ref_contains_sha_response(stream, collection_name)
+ stream.each_with_object([]) do |response, array|
+ encoded_names = response.send(collection_name).map { |b| Gitlab::Git.ref_name(b) } # rubocop:disable GitlabSecurity/PublicSend
+ array.concat(encoded_names)
+ end
+ end
+
def invalid_ref!(message)
raise Gitlab::Git::Repository::InvalidRef.new(message)
end
--
cgit v1.2.1
From fe2e8dba22471eb39a703672c5d8039c20e94fa6 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Thu, 1 Feb 2018 12:26:55 +0000
Subject: fixed infinite loop crashing tests
---
spec/javascripts/pager_spec.js | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js
index fd9b83e3514..b09494f0b77 100644
--- a/spec/javascripts/pager_spec.js
+++ b/spec/javascripts/pager_spec.js
@@ -47,12 +47,12 @@ describe('pager', () => {
});
describe('getOld', () => {
- const urlRegex = /\/some_list(.*)$/;
+ const urlRegex = /(.*)some_list(.*)$/;
let mock;
function mockSuccess() {
mock.onGet(urlRegex).reply(200, {
- count: 20,
+ count: 0,
html: '',
});
}
@@ -65,9 +65,9 @@ describe('pager', () => {
setFixtures('
');
spyOn(axios, 'get').and.callThrough();
- Pager.init();
-
mock = new MockAdapter(axios);
+
+ Pager.init();
});
afterEach(() => {
@@ -119,10 +119,15 @@ describe('pager', () => {
Pager.getOld();
setTimeout(() => {
- const [url, params] = $.ajax.calls.argsFor(0);
- console.log(url, params);
- // expect(data).toBe('limit=20&offset=100');
- // expect(url).toBe('/some_list');
+ const [url, params] = axios.get.calls.argsFor(0);
+
+ expect(params).toEqual({
+ params: {
+ limit: 20,
+ offset: 100,
+ },
+ });
+ expect(url).toBe('/some_list');
done();
});
--
cgit v1.2.1
From a51dea0d43dd9a7995c710558b18da4f2696e284 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Thu, 1 Feb 2018 13:37:47 +0100
Subject: Improve doc/development/automatic_ce_ee_merge.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
doc/development/automatic_ce_ee_merge.md | 31 +++++++++++++++++++++++--------
1 file changed, 23 insertions(+), 8 deletions(-)
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index 5a784b6de06..cf6314f9521 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -5,17 +5,32 @@ Enterprise Edition (look for the [`CE Upstream` merge requests]).
This merge is done automatically in a
[scheduled pipeline](https://gitlab.com/gitlab-org/release-tools/-/jobs/43201679).
-If a merge is already in progress, the job [doesn't create a new one](https://gitlab.com/gitlab-org/release-tools/-/jobs/43157687).
-**If you are pinged in a `CE Upstream` merge request to resolve a conflict,
-please resolve the conflict as soon as possible or ask someone else to do it!**
-
->**Note:**
-It's ok to resolve more conflicts than the one that you are asked to resolve. In
-that case, it's a good habit to ask for a double-check on your resolution by
-someone who is familiar with the code you touched.
+## What to do if you are pinged in a `CE Upstream` merge request to resolve a conflict?
+
+1. Please resolve the conflict as soon as possible or ask someone else to do it
+ - It's ok to resolve more conflicts than the one that you are asked to resolve.
+ In that case, it's a good habit to ask for a double-check on your resolution
+ by someone who is familiar with the code you touched.
+1. Once you have resolved your conflicts, push to the branch (no force-push)
+1. Assign the merge request to the next person that has to resolve a conflict
+1. If all conflicts are resolved after your resolution is pushed, keep the merge
+ request assigned to you: **you are now responsible for the merge request to be
+ green**
+1. If you need any help, you can ping the current [release managers], or ask in
+ the `#ce-to-ee` Slack channel
+
+A few notes about the automatic CE->EE merge job:
+
+- If a merge is already in progress, the job
+ [doesn't create a new one](https://gitlab.com/gitlab-org/release-tools/-/jobs/43157687).
+- If there is nothing to merge (i.e. EE is up-to-date with CE), the job doesn't
+ create a new one
+- The job posts messages to the `#ce-to-ee` Slack channel to inform what's the
+ current CE->EE merge status (e.g. "A new MR has been created", "A MR is still pending")
[`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream
+[release managers]: https://about.gitlab.com/release-managers/
## Always merge EE merge requests before their CE counterparts
--
cgit v1.2.1
From 3d89a03e727e9e991f0caab74ce31d4c7a1c0246 Mon Sep 17 00:00:00 2001
From: Filipa Lacerda
Date: Thu, 1 Feb 2018 13:05:37 +0000
Subject: Moves widget header into a vue file
---
.../components/mr_widget_header.js | 117 ---------
.../components/mr_widget_header.vue | 139 +++++++++++
.../components/mr_widget_merge_help.vue | 4 +-
.../vue_merge_request_widget/dependencies.js | 2 +-
.../components/mr_widget_header_spec.js | 273 ++++++++++++++-------
.../components/mr_widget_merge_help_spec.js | 28 ++-
6 files changed, 351 insertions(+), 212 deletions(-)
delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
deleted file mode 100644
index de6e5149a87..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import tooltip from '../../vue_shared/directives/tooltip';
-import { pluralize } from '../../lib/utils/text_utility';
-import icon from '../../vue_shared/components/icon.vue';
-
-export default {
- name: 'MRWidgetHeader',
- props: {
- mr: { type: Object, required: true },
- },
- directives: {
- tooltip,
- },
- components: {
- icon,
- },
- computed: {
- shouldShowCommitsBehindText() {
- return this.mr.divergedCommitsCount > 0;
- },
- commitsText() {
- return pluralize('commit', this.mr.divergedCommitsCount);
- },
- branchNameClipboardData() {
- // This supports code in app/assets/javascripts/copy_to_clipboard.js that
- // works around ClipboardJS limitations to allow the context-specific
- // copy/pasting of plain text or GFM.
- return JSON.stringify({
- text: this.mr.sourceBranch,
- gfm: `\`${this.mr.sourceBranch}\``,
- });
- },
- },
- methods: {
- isBranchTitleLong(branchTitle) {
- return branchTitle.length > 32;
- },
- },
- template: `
-
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
new file mode 100644
index 00000000000..b82026ee1bb
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+ {{ s__("mrWidget|Check out branch") }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
index bc27bbdd364..fe09c53272e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
@@ -7,14 +7,14 @@
missingBranch: {
type: String,
required: false,
- default: ''
+ default: '',
},
},
computed: {
missingBranchInfo() {
return sprintf(
s__('mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the'),
- { branch: this.missingBranch }
+ { branch: this.missingBranch },
);
},
},
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 0e869a2aaac..04c09d4eda7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -11,7 +11,7 @@
export { default as Vue } from 'vue';
export { default as SmartInterval } from '~/smart_interval';
-export { default as WidgetHeader } from './components/mr_widget_header';
+export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
export { default as WidgetDeployment } from './components/mr_widget_deployment';
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 93bb83ca8bd..5fb7093a078 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -1,121 +1,220 @@
import Vue from 'vue';
-import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header';
-
-const createComponent = (mr) => {
- const Component = Vue.extend(headerComponent);
- return new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
-};
+import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('MRWidgetHeader', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = headerComponent.props;
+ let vm;
+ let Component;
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
+ beforeEach(() => {
+ Component = Vue.extend(headerComponent);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
});
describe('computed', () => {
- let vm;
- beforeEach(() => {
- vm = createComponent({
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '/foo/bar/mr-widget-refactor',
- targetBranch: 'master',
+ describe('shouldShowCommitsBehindText', () => {
+ it('return true when there are divergedCommitsCount', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'Link ',
+ targetBranch: 'master',
+ } });
+
+ expect(vm.shouldShowCommitsBehindText).toEqual(true);
+ });
+
+ it('returns false where there are no divergedComits count', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 0,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'Link ',
+ targetBranch: 'master',
+ } });
+ expect(vm.shouldShowCommitsBehindText).toEqual(false);
});
});
- it('shouldShowCommitsBehindText', () => {
- expect(vm.shouldShowCommitsBehindText).toBeTruthy();
+ describe('commitsText', () => {
+ it('returns singular when there is one commit', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 1,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'Link ',
+ targetBranch: 'master',
+ } });
- vm.mr.divergedCommitsCount = 0;
- expect(vm.shouldShowCommitsBehindText).toBeFalsy();
- });
+ expect(vm.commitsText).toEqual('commit behind');
+ });
- it('commitsText', () => {
- expect(vm.commitsText).toEqual('commits');
+ it('returns plural when there is more than one commit', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 2,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'Link ',
+ targetBranch: 'master',
+ } });
- vm.mr.divergedCommitsCount = 1;
- expect(vm.commitsText).toEqual('commit');
+ expect(vm.commitsText).toEqual('commits behind');
+ });
});
});
describe('template', () => {
- let vm;
- let el;
- let mr;
- const sourceBranchPath = '/foo/bar/mr-widget-refactor';
-
- beforeEach(() => {
- mr = {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: `mr-widget-refactor `,
- sourceBranchRemoved: false,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- };
-
- vm = createComponent(mr);
- el = vm.$el;
+ describe('common elements', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'mr-widget-refactor ',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('renders source branch link', () => {
+ expect(
+ vm.$el.querySelector('.js-source-branch').innerHTML,
+ ).toEqual('mr-widget-refactor ');
+ });
+
+ it('renders clipboard button', () => {
+ expect(vm.$el.querySelector('.btn-clipboard')).not.toEqual(null);
+ });
+
+ it('renders target branch', () => {
+ expect(vm.$el.querySelector('.js-target-branch').textContent.trim()).toEqual('master');
+ });
});
- it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-source-target')).toBeTruthy();
- const sourceBranchLink = el.querySelectorAll('.label-branch')[0];
- const targetBranchLink = el.querySelectorAll('.label-branch')[1];
- const commitsCount = el.querySelector('.diverged-commits-count');
+ describe('with an open merge request', () => {
+ afterEach(() => {
+ vm.$destroy();
+ });
- expect(sourceBranchLink.textContent).toContain(mr.sourceBranch);
- expect(targetBranchLink.textContent).toContain(mr.targetBranch);
- expect(sourceBranchLink.querySelector('a').getAttribute('href')).toEqual(sourceBranchPath);
- expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchTreePath);
- expect(commitsCount.textContent).toContain('12 commits behind');
- expect(commitsCount.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath);
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'mr-widget-refactor ',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('renders checkout branch button with modal trigger', () => {
+ const button = vm.$el.querySelector('.js-check-out-branch');
+
+ expect(button.textContent.trim()).toEqual('Check out branch');
+ expect(button.getAttribute('data-target')).toEqual('#modal_merge_info');
+ expect(button.getAttribute('data-toggle')).toEqual('modal');
+ });
+
+ it('renders download dropdown with links', () => {
+ expect(
+ vm.$el.querySelector('.js-download-email-patches').textContent.trim(),
+ ).toEqual('Email patches');
- expect(el.textContent).toContain('Check out branch');
- expect(el.querySelectorAll('.dropdown li a')[0].getAttribute('href')).toEqual(mr.emailPatchesPath);
- expect(el.querySelectorAll('.dropdown li a')[1].getAttribute('href')).toEqual(mr.plainDiffPath);
+ expect(
+ vm.$el.querySelector('.js-download-email-patches').getAttribute('href'),
+ ).toEqual('/mr/email-patches');
- expect(el.querySelector('a[href="#modal_merge_info"]').getAttribute('disabled')).toBeNull();
+ expect(
+ vm.$el.querySelector('.js-download-plain-diff').textContent.trim(),
+ ).toEqual('Plain diff');
+
+ expect(
+ vm.$el.querySelector('.js-download-plain-diff').getAttribute('href'),
+ ).toEqual('/mr/plainDiffPath');
+ });
});
- it('should not have right action links if the MR state is not open', (done) => {
- vm.mr.isOpen = false;
- Vue.nextTick(() => {
- expect(el.textContent).not.toContain('Check out branch');
- expect(el.querySelectorAll('.dropdown li a').length).toEqual(0);
- done();
+ describe('with a closed merge request', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'mr-widget-refactor ',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: false,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('does not render checkout branch button with modal trigger', () => {
+ const button = vm.$el.querySelector('.js-check-out-branch');
+
+ expect(button).toEqual(null);
+ });
+
+ it('does not render download dropdown with links', () => {
+ expect(
+ vm.$el.querySelector('.js-download-email-patches'),
+ ).toEqual(null);
+
+ expect(
+ vm.$el.querySelector('.js-download-plain-diff'),
+ ).toEqual(null);
});
});
- it('should not render diverged commits count if the MR has no diverged commits', (done) => {
- vm.mr.divergedCommitsCount = null;
- Vue.nextTick(() => {
- expect(el.textContent).not.toContain('commits behind');
- expect(el.querySelectorAll('.diverged-commits-count').length).toEqual(0);
- done();
+ describe('without diverged commits', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 0,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'mr-widget-refactor ',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('does not render diverged commits info', () => {
+ expect(vm.$el.querySelector('.diverged-commits-count')).toEqual(null);
});
});
- it('should disable check out branch button if source branch has been removed', (done) => {
- vm.mr.sourceBranchRemoved = true;
+ describe('with diverged commits', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: 'mr-widget-refactor ',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
- Vue.nextTick()
- .then(() => {
- expect(el.querySelector('a[href="#modal_merge_info"]').getAttribute('disabled')).toBe('disabled');
- done();
- })
- .catch(done.fail);
+ it('renders diverged commits info', () => {
+ expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual('(12 commits behind)');
+ });
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
index 1ed872fafe7..f6656ad2e80 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
@@ -2,9 +2,6 @@ import Vue from 'vue';
import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
-
-const text = `If the ${props.missingBranch} branch exists in your local repository`;
-
describe('MRWidgetMergeHelp', () => {
let vm;
let Component;
@@ -17,7 +14,7 @@ describe('MRWidgetMergeHelp', () => {
vm.$destroy();
});
- fdescribe('with missing branch', () => {
+ describe('with missing branch', () => {
beforeEach(() => {
vm = mountComponent(Component, {
missingBranch: 'this-is-not-the-branch-you-are-looking-for',
@@ -25,8 +22,16 @@ describe('MRWidgetMergeHelp', () => {
});
it('renders missing branch information', () => {
- console.log('', vm.$el);
+ expect(
+ vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ').replace(/\s\s+/g, ' '),
+ ).toEqual(
+ 'If the this-is-not-the-branch-you-are-looking-for branch exists in your local repository, you can merge this merge request manually using the command line',
+ );
+ });
+ it('renders element to open a modal', () => {
+ expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info');
+ expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal');
});
});
@@ -34,5 +39,18 @@ describe('MRWidgetMergeHelp', () => {
beforeEach(() => {
vm = mountComponent(Component);
});
+
+ it('renders information about how to merge manually', () => {
+ expect(
+ vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ').replace(/\s\s+/g, ' '),
+ ).toEqual(
+ 'You can merge this merge request manually using the command line',
+ );
+ });
+
+ it('renders element to open a modal', () => {
+ expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info');
+ expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal');
+ });
});
});
--
cgit v1.2.1
From a82a31c799aadb0000d305dccfa0a47de2e13e9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Thu, 1 Feb 2018 09:35:26 +0100
Subject: Ensure we save QA screenshots to an absolute path
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This allows to open the file address printed in the terminal, otherwise
the address started with `tmp` which was relatove to the `qa/` dir.
Signed-off-by: Rémy Coutable
---
qa/qa/runtime/browser.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index ce888b51ea5..a12d95683af 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -84,7 +84,7 @@ module QA
config.javascript_driver = :chrome
config.default_max_wait_time = 10
# https://github.com/mattheworiordan/capybara-screenshot/issues/164
- config.save_path = 'tmp'
+ config.save_path = File.expand_path('../../tmp', __dir__)
end
end
--
cgit v1.2.1
From 922154ce44e0e9a86b3c1d28e4e0c35e03f0bb08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Thu, 1 Feb 2018 15:11:33 +0100
Subject: QA::Page::Component::Dropzone#initialize needs a QA::Page::Base
object
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
qa/qa/page/component/dropzone.rb | 2 ++
qa/qa/page/project/issue/show.rb | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/qa/qa/page/component/dropzone.rb b/qa/qa/page/component/dropzone.rb
index 5e6fdff20eb..15bdc742fda 100644
--- a/qa/qa/page/component/dropzone.rb
+++ b/qa/qa/page/component/dropzone.rb
@@ -4,6 +4,8 @@ module QA
class Dropzone
attr_reader :page, :container
+ # page - A QA::Page::Base object
+ # container - CSS selector of the comment textarea's container
def initialize(page, container)
@page = page
@container = container
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 364a2c61665..5bc0598a524 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -27,7 +27,7 @@ module QA
fill_in(with: text, name: 'note[note]')
unless attachment.nil?
- QA::Page::Component::Dropzone.new(page, '.new-note')
+ QA::Page::Component::Dropzone.new(self, '.new-note')
.attach_file(attachment)
end
--
cgit v1.2.1
From 4aedec11922c1794652c342a8cb9767a1ad04756 Mon Sep 17 00:00:00 2001
From: Jacob Vosmaer
Date: Thu, 1 Feb 2018 15:44:36 +0100
Subject: Ban Rugged from Repository
---
scripts/lint-rugged | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/scripts/lint-rugged b/scripts/lint-rugged
index 03f780f880b..cabd083e9f9 100755
--- a/scripts/lint-rugged
+++ b/scripts/lint-rugged
@@ -1,7 +1,7 @@
#!/usr/bin/env ruby
ALLOWED = [
- # Can be deleted (?) once rugged is no longer used in production. Doesn't make Rugged calls.
+ # Can be fixed once Rugged is no longer used in production. Doesn't make Rugged calls.
'config/initializers/8_metrics.rb',
# Can be deleted once wiki's are fully (mandatory) migrated
@@ -13,9 +13,6 @@ ALLOWED = [
# Needs to be migrated, https://gitlab.com/gitlab-org/gitaly/issues/954
'lib/tasks/gitlab/cleanup.rake',
- # https://gitlab.com/gitlab-org/gitaly/issues/961
- 'app/models/repository.rb',
-
# The only place where Rugged code is still allowed in production
'lib/gitlab/git/'
].freeze
--
cgit v1.2.1
From c8a17cd1bd5fe16b6374ba93cc70a04cdeedbe21 Mon Sep 17 00:00:00 2001
From: Filipa Lacerda
Date: Thu, 1 Feb 2018 15:16:34 +0000
Subject: Update spec to match link being changed into a button
---
spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
index fb73ab05f87..dbca279569a 100644
--- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
+++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
@@ -61,7 +61,7 @@ describe 'Merge request > User selects branches for new MR', :js do
fill_in "merge_request_title", with: "Orphaned MR test"
click_button "Submit merge request"
- click_link "Check out branch"
+ click_button "Check out branch"
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
--
cgit v1.2.1
From f01e9c1ef65d77d10807f98cde9ce5f5911787c8 Mon Sep 17 00:00:00 2001
From: Yorick Peterse
Date: Thu, 1 Feb 2018 16:23:32 +0100
Subject: Finish any remaining jobs for issues.closed_at
In the event of Sidekiq jobs getting lost there may be some rows left to
migrate. This migration ensures any remaining jobs are completed and
that all data has been migrated.
This fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/41595
---
changelogs/unreleased/issues-closed-at-steal.yml | 5 +++
...201145907_migrate_remaining_issues_closed_at.rb | 42 ++++++++++++++++++++++
db/schema.rb | 2 +-
3 files changed, 48 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/issues-closed-at-steal.yml
create mode 100644 db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb
diff --git a/changelogs/unreleased/issues-closed-at-steal.yml b/changelogs/unreleased/issues-closed-at-steal.yml
new file mode 100644
index 00000000000..a5f0898995f
--- /dev/null
+++ b/changelogs/unreleased/issues-closed-at-steal.yml
@@ -0,0 +1,5 @@
+---
+title: Finish any remaining jobs for issues.closed_at
+merge_request:
+author:
+type: other
diff --git a/db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb b/db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb
new file mode 100644
index 00000000000..7cb913bb2bf
--- /dev/null
+++ b/db/migrate/20180201145907_migrate_remaining_issues_closed_at.rb
@@ -0,0 +1,42 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class Issue < ActiveRecord::Base
+ self.table_name = 'issues'
+ include EachBatch
+ end
+
+ def up
+ Gitlab::BackgroundMigration.steal('CopyColumn')
+ Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange')
+
+ # It's possible the cleanup job was killed which means we need to manually
+ # migrate any remaining rows.
+ migrate_remaining_rows if migrate_column_type?
+ end
+
+ def down
+ end
+
+ def migrate_remaining_rows
+ Issue.where('closed_at_for_type_change IS NULL AND closed_at IS NOT NULL').each_batch do |batch|
+ batch.update_all('closed_at_for_type_change = closed_at')
+ end
+
+ cleanup_concurrent_column_type_change(:issues, :closed_at)
+ end
+
+ def migrate_column_type?
+ # Some environments may have already executed the previous version of this
+ # migration, thus we don't need to migrate those environments again.
+ column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4e82a688725..0d97b6f9ddd 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180115201419) do
+ActiveRecord::Schema.define(version: 20180201145907) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
--
cgit v1.2.1
From 1123d9dc460353cbc3b46606cc2235f0433f35e1 Mon Sep 17 00:00:00 2001
From: Jose Ivan Vargas
Date: Tue, 23 Jan 2018 12:50:58 -0600
Subject: Added more tests and corrected typos
---
.../javascripts/pages/projects/show/index.js | 1 -
.../javascripts/pages/projects/tree/show/index.js | 35 +++++----
.../commit_pipeline_status_component.vue | 78 ++++++++++++--------
app/assets/stylesheets/pages/commits.scss | 10 ++-
app/views/projects/commits/_commit.html.haml | 2 +-
features/project/project.feature | 1 -
features/steps/shared/project.rb | 1 -
.../commit_pipeline_status_component_spec.js | 85 +++++++++++++++-------
8 files changed, 131 insertions(+), 82 deletions(-)
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 4c42fda16d7..55154cdddcb 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -5,7 +5,6 @@ import TreeView from '~/tree';
import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
-import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index f14c3f86687..c4b3356e478 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -15,24 +15,23 @@ export default () => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
- const $statusLink = $('.ci-status-link');
- if ($statusLink.length > 0) {
- $statusLink.remove();
+ const statusLink = document.querySelector('.commit-actions .ci-status-link');
+ if (statusLink != null) {
+ statusLink.remove();
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: commitPipelineStatusEl,
+ components: {
+ commitPipelineStatus,
+ },
+ render(createElement) {
+ return createElement('commit-pipeline-status', {
+ props: {
+ endpoint: commitPipelineStatusEl.dataset.endpoint,
+ },
+ });
+ },
+ });
}
- commitPipelineStatusEl.classList.remove('hidden');
- // eslint-disable-next-line no-new
- new Vue({
- el: '#commit-pipeline-status',
- components: {
- commitPipelineStatus,
- },
- render(createElement) {
- return createElement('commit-pipeline-status', {
- props: {
- endpoint: commitPipelineStatusEl.dataset.endpoint,
- },
- });
- },
- });
};
diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
index e13acf8555a..63f20a0041d 100644
--- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -1,8 +1,10 @@
-
-
-
-
+
-
+
+
+
+
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index adfd72556b8..17801ed5910 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -196,6 +196,14 @@
@media (min-width: $screen-sm-min) {
font-size: 0;
+ div {
+ display: inline;
+ }
+
+ .fa-spinner {
+ font-size: 12px;
+ }
+
span {
font-size: 6px;
}
@@ -226,7 +234,7 @@
.ci-status-icon {
position: relative;
- top: 3px;
+ top: 1px;
}
}
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index c94e10947e6..90272ad9554 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -51,7 +51,7 @@
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
- #commit-pipeline-status.hidden{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
+ #commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
= link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
diff --git a/features/project/project.feature b/features/project/project.feature
index bcd72c5c5a3..23817ef3ac9 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -23,7 +23,6 @@ Feature: Project
And I visit project "Shop" page
Then I should see project "Shop" README
- @javascript
Scenario: I should see last commit with CI
Given project "Shop" has CI enabled
Given project "Shop" has CI build
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 923d54a6545..affbccccdf9 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -218,7 +218,6 @@ module SharedProject
end
step 'I should see last commit with CI status' do
- sleep 2
page.within ".blob-commit-info" do
expect(page).to have_content(project.commit.sha[0..6])
expect(page).to have_link("Commit: skipped")
diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
index 2a52097e0d5..90f290e845e 100644
--- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js
+++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
@@ -6,7 +6,7 @@ import mountComponent from '../helpers/vue_mount_component_helper';
describe('Commit pipeline status component', () => {
let vm;
- let component;
+ let Component;
let mock;
const mockCiStatus = {
details_path: '/root/hello-world/pipelines/1',
@@ -19,34 +19,25 @@ describe('Commit pipeline status component', () => {
};
beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet('/dummy/endpoint').reply(() => {
- const res = Promise.resolve([200, {
- pipelines: [
- {
- details: {
- stages: [
- {
- status: mockCiStatus,
- title: 'Commit: canceled',
- },
- ],
- },
- },
- ],
- }]);
- return res;
- });
- component = Vue.extend(commitPipelineStatus);
- });
-
- afterEach(() => {
- mock.reset();
+ Component = Vue.extend(commitPipelineStatus);
});
- describe('While polling pipeline data', () => {
+ describe('While polling pipeline data succesfully', () => {
beforeEach(() => {
- vm = mountComponent(component, {
+ mock = new MockAdapter(axios);
+ mock.onGet('/dummy/endpoint').reply(() => {
+ const res = Promise.resolve([200, {
+ pipelines: [
+ {
+ details: {
+ status: mockCiStatus,
+ },
+ },
+ ],
+ }]);
+ return res;
+ });
+ vm = mountComponent(Component, {
endpoint: '/dummy/endpoint',
});
});
@@ -54,18 +45,58 @@ describe('Commit pipeline status component', () => {
afterEach(() => {
vm.poll.stop();
vm.$destroy();
+ mock.restore();
+ });
+
+ it('shows the loading icon when polling is starting', (done) => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.loading-container')).toBe(null);
+ done();
+ });
});
it('contains a ciStatus when the polling is succesful ', (done) => {
setTimeout(() => {
expect(vm.ciStatus).toEqual(mockCiStatus);
done();
- }, 1000);
+ });
});
it('contains a ci-status icon when polling is succesful', (done) => {
setTimeout(() => {
expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
+ expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(`ci-status-icon-${mockCiStatus.group}`);
+ done();
+ });
+ });
+ });
+
+ describe('When polling data was not succesful', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet('/dummy/endpoint').reply(() => {
+ const res = Promise.reject([502, { }]);
+ return res;
+ });
+ vm = new Component({
+ props: {
+ endpoint: '/dummy/endpoint',
+ },
+ });
+ });
+
+ afterEach(() => {
+ vm.poll.stop();
+ vm.$destroy();
+ mock.restore();
+ });
+
+ it('calls an errorCallback', (done) => {
+ spyOn(vm, 'errorCallback').and.callThrough();
+ vm.$mount();
+ setTimeout(() => {
+ expect(vm.errorCallback.calls.count()).toEqual(1);
done();
});
});
--
cgit v1.2.1
From 3fed0302cb383ca3714c2fc540ac777409020862 Mon Sep 17 00:00:00 2001
From: Filipa Lacerda
Date: Thu, 1 Feb 2018 16:13:10 +0000
Subject: Transform `a` tag with button role into a `button`
---
.../components/mr_widget_merge_help.vue | 10 ++++++----
.../vue_mr_widget/components/mr_widget_merge_help_spec.js | 10 +++++-----
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
index fe09c53272e..62b61e1f41f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
@@ -29,11 +29,13 @@
{{ s__("mrWidget|You can merge this merge request manually using the") }}
-
+ data-target="#modal_merge_info"
+ >
{{ s__("mrWidget|command line") }}
-
+
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
index f6656ad2e80..cc43639f576 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
@@ -29,9 +29,9 @@ describe('MRWidgetMergeHelp', () => {
);
});
- it('renders element to open a modal', () => {
- expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info');
- expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal');
+ it('renders button to open help modal', () => {
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual('#modal_merge_info');
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual('modal');
});
});
@@ -49,8 +49,8 @@ describe('MRWidgetMergeHelp', () => {
});
it('renders element to open a modal', () => {
- expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('#modal_merge_info');
- expect(vm.$el.querySelector('a').getAttribute('data-toggle')).toEqual('modal');
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual('#modal_merge_info');
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual('modal');
});
});
});
--
cgit v1.2.1
From 9ee5a9039b0d8cde6f0eef290b90a6dca229016e Mon Sep 17 00:00:00 2001
From: Alexandre Figura
Date: Thu, 1 Feb 2018 16:41:47 +0000
Subject: Fix typo in gitlab_runner_chart.md
When Gitlab URL is set as `gitlabURL` in the configuration file, `helm install` is not happy and complains about a missing `gitlabUrl`.
---
doc/install/kubernetes/gitlab_runner_chart.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index ca9c95aeced..1f53e12d5f8 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -31,7 +31,7 @@ The default configuration can always be found in the [values.yaml](https://gitla
In order for GitLab Runner to function, your config file **must** specify the following:
- - `gitlabURL` - the GitLab Server URL (with protocol) to register the runner against
+ - `gitlabUrl` - the GitLab Server URL (with protocol) to register the runner against
- `runnerRegistrationToken` - The Registration Token for adding new Runners to the GitLab Server. This must be
retrieved from your GitLab Instance. See the [GitLab Runner Documentation](../../ci/runners/README.md#creating-and-registering-a-runner) for more information.
@@ -47,7 +47,7 @@ Here is a snippet of the important settings:
## The GitLab Server URL (with protocol) that want to register the runner against
## ref: https://docs.gitlab.com/runner/commands/README.html#gitlab-runner-register
##
-gitlabURL: http://gitlab.your-domain.com/
+gitlabUrl: http://gitlab.your-domain.com/
## The Registration Token for adding new Runners to the GitLab Server. This must
## be retreived from your GitLab Instance.
--
cgit v1.2.1
From 8128ecaecc038224285d713ca08b136462d6f365 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?=
Date: Thu, 1 Feb 2018 18:01:47 +0100
Subject: Speed-up the gitlab_git_test job
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Rémy Coutable
---
.gitlab-ci.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index be18520b876..b4afa953175 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -738,8 +738,9 @@ cache gems:
gitlab_git_test:
<<: *dedicated-runner
<<: *except-docs-and-qa
- <<: *pull-cache
variables:
SETUP_DB: "false"
+ before_script: []
+ cache: {}
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
--
cgit v1.2.1
From 2057a6acdee7c1f6824ff6289b0d979e8cb15f35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mica=C3=ABl=20Bergeron?=
Date: Mon, 29 Jan 2018 12:57:34 -0500
Subject: port of 594e6a0a625^..f74c90f68c6
---
app/controllers/concerns/uploads_actions.rb | 61 +++++++++--
app/controllers/groups/uploads_controller.rb | 30 ++---
app/controllers/projects/lfs_storage_controller.rb | 2 +-
app/controllers/projects/uploads_controller.rb | 21 ++--
app/controllers/uploads_controller.rb | 75 +++++--------
app/models/appearance.rb | 1 +
app/models/concerns/avatarable.rb | 24 ++++
app/models/group.rb | 13 +--
app/models/note.rb | 1 +
app/models/project.rb | 15 +--
app/models/upload.rb | 49 +++++----
app/models/user.rb | 16 +--
.../hashed_storage/migrate_attachments_service.rb | 4 +-
app/uploaders/attachment_uploader.rb | 8 +-
app/uploaders/avatar_uploader.rb | 19 ++--
app/uploaders/file_mover.rb | 3 +-
app/uploaders/file_uploader.rb | 118 ++++++++++++++------
app/uploaders/gitlab_uploader.rb | 77 +++++++------
app/uploaders/job_artifact_uploader.rb | 26 +----
app/uploaders/legacy_artifact_uploader.rb | 26 +----
app/uploaders/lfs_object_uploader.rb | 21 ++--
app/uploaders/namespace_file_uploader.rb | 18 +--
app/uploaders/personal_file_uploader.rb | 30 ++---
app/uploaders/records_uploads.rb | 80 +++++++++-----
app/uploaders/uploader_helper.rb | 9 +-
app/uploaders/workhorse.rb | 7 ++
app/workers/upload_checksum_worker.rb | 2 +-
config/gitlab.yml.example | 8 ++
config/initializers/1_settings.rb | 13 ++-
...20180119135717_add_uploader_index_to_uploads.rb | 20 ++++
doc/development/file_storage.md | 104 ++++++++++++++++--
lib/api/runner.rb | 6 +-
lib/backup/artifacts.rb | 2 +-
.../populate_untracked_uploads.rb | 2 +-
.../prepare_untracked_uploads.rb | 9 +-
lib/gitlab/gfm/uploads_rewriter.rb | 2 +-
lib/gitlab/import_export/uploads_saver.rb | 8 +-
lib/gitlab/uploads_transfer.rb | 2 +-
lib/gitlab/workhorse.rb | 9 +-
spec/controllers/groups/uploads_controller_spec.rb | 4 +-
.../projects/artifacts_controller_spec.rb | 3 +-
spec/controllers/projects/raw_controller_spec.rb | 2 +-
spec/controllers/uploads_controller_spec.rb | 13 +++
spec/factories/notes.rb | 4 +-
spec/factories/uploads.rb | 26 ++++-
.../prepare_untracked_uploads_spec.rb | 53 ++++-----
spec/lib/gitlab/gfm/uploads_rewriter_spec.rb | 2 +-
.../gitlab/import_export/uploads_restorer_spec.rb | 9 +-
.../lib/gitlab/import_export/uploads_saver_spec.rb | 4 +-
spec/models/namespace_spec.rb | 2 +-
spec/models/upload_spec.rb | 73 +++----------
spec/requests/api/runner_spec.rb | 4 +-
spec/requests/lfs_http_spec.rb | 4 +-
spec/services/issues/move_service_spec.rb | 4 +-
.../migrate_attachments_service_spec.rb | 4 +-
.../controllers/uploads_actions_shared_examples.rb | 62 ++++++-----
.../uploaders/gitlab_uploader_shared_examples.rb | 48 ++++++++
spec/support/test_env.rb | 2 +-
spec/support/track_untracked_uploads_helpers.rb | 2 +-
spec/uploaders/attachment_uploader_spec.rb | 18 ++-
spec/uploaders/avatar_uploader_spec.rb | 18 ++-
spec/uploaders/file_mover_spec.rb | 18 +--
spec/uploaders/file_uploader_spec.rb | 121 +++++----------------
spec/uploaders/job_artifact_uploader_spec.rb | 32 +-----
spec/uploaders/legacy_artifact_uploader_spec.rb | 49 ++-------
spec/uploaders/lfs_object_uploader_spec.rb | 38 +------
spec/uploaders/namespace_file_uploader_spec.rb | 21 ++--
spec/uploaders/personal_file_uploader_spec.rb | 28 ++---
spec/uploaders/records_uploads_spec.rb | 73 ++++++-------
spec/workers/upload_checksum_worker_spec.rb | 29 +++--
70 files changed, 910 insertions(+), 801 deletions(-)
create mode 100644 app/uploaders/workhorse.rb
create mode 100644 db/migrate/20180119135717_add_uploader_index_to_uploads.rb
create mode 100644 spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index a6fb1f40001..61554029d09 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -1,6 +1,8 @@
module UploadsActions
include Gitlab::Utils::StrongMemoize
+ UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo).freeze
+
def create
link_to_file = UploadService.new(model, params[:file], uploader_class).execute
@@ -17,34 +19,71 @@ module UploadsActions
end
end
+ # This should either
+ # - send the file directly
+ # - or redirect to its URL
+ #
def show
return render_404 unless uploader.exists?
- disposition = uploader.image_or_video? ? 'inline' : 'attachment'
-
- expires_in 0.seconds, must_revalidate: true, private: true
+ if uploader.file_storage?
+ disposition = uploader.image_or_video? ? 'inline' : 'attachment'
+ expires_in 0.seconds, must_revalidate: true, private: true
- send_file uploader.file.path, disposition: disposition
+ send_file uploader.file.path, disposition: disposition
+ else
+ redirect_to uploader.url
+ end
end
private
+ def uploader_class
+ raise NotImplementedError
+ end
+
+ def upload_mount
+ mounted_as = params[:mounted_as]
+ mounted_as if UPLOAD_MOUNTS.include?(mounted_as)
+ end
+
+ def uploader_mounted?
+ upload_model_class < CarrierWave::Mount::Extension && !upload_mount.nil?
+ end
+
def uploader
strong_memoize(:uploader) do
- return if show_model.nil?
+ if uploader_mounted?
+ model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend
+ else
+ build_uploader_from_upload || build_uploader_from_params
+ end
+ end
+ end
- file_uploader = FileUploader.new(show_model, params[:secret])
- file_uploader.retrieve_from_store!(params[:filename])
+ def build_uploader_from_upload
+ return nil unless params[:secret] && params[:filename]
- file_uploader
- end
+ upload_path = uploader_class.upload_path(params[:secret], params[:filename])
+ upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_path)
+ upload&.build_uploader
+ end
+
+ def build_uploader_from_params
+ uploader = uploader_class.new(model, params[:secret])
+ uploader.retrieve_from_store!(params[:filename])
+ uploader
end
def image_or_video?
uploader && uploader.exists? && uploader.image_or_video?
end
- def uploader_class
- FileUploader
+ def find_model
+ nil
+ end
+
+ def model
+ strong_memoize(:model) { find_model }
end
end
diff --git a/app/controllers/groups/uploads_controller.rb b/app/controllers/groups/uploads_controller.rb
index e6bd9806401..f1578f75e88 100644
--- a/app/controllers/groups/uploads_controller.rb
+++ b/app/controllers/groups/uploads_controller.rb
@@ -7,29 +7,23 @@ class Groups::UploadsController < Groups::ApplicationController
private
- def show_model
- strong_memoize(:show_model) do
- group_id = params[:group_id]
-
- Group.find_by_full_path(group_id)
- end
+ def upload_model_class
+ Group
end
- def authorize_upload_file!
- render_404 unless can?(current_user, :upload_file, group)
+ def uploader_class
+ NamespaceFileUploader
end
- def uploader
- strong_memoize(:uploader) do
- file_uploader = uploader_class.new(show_model, params[:secret])
- file_uploader.retrieve_from_store!(params[:filename])
- file_uploader
- end
- end
+ def find_model
+ return @group if @group
- def uploader_class
- NamespaceFileUploader
+ group_id = params[:group_id]
+
+ Group.find_by_full_path(group_id)
end
- alias_method :model, :group
+ def authorize_upload_file!
+ render_404 unless can?(current_user, :upload_file, group)
+ end
end
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
index 293869345bd..941638db427 100644
--- a/app/controllers/projects/lfs_storage_controller.rb
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -60,7 +60,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
def store_file(oid, size, tmp_file)
# Define tmp_file_path early because we use it in "ensure"
- tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file)
+ tmp_file_path = File.join(LfsObjectUploader.workhorse_upload_path, tmp_file)
object = LfsObject.find_or_create_by(oid: oid, size: size)
file_exists = object.file.exists? || move_tmp_file_to_storage(object, tmp_file_path)
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 4685bbe80b4..f5cf089ad98 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -1,6 +1,7 @@
class Projects::UploadsController < Projects::ApplicationController
include UploadsActions
+ # These will kick you out if you don't have access.
skip_before_action :project, :repository,
if: -> { action_name == 'show' && image_or_video? }
@@ -8,14 +9,20 @@ class Projects::UploadsController < Projects::ApplicationController
private
- def show_model
- strong_memoize(:show_model) do
- namespace = params[:namespace_id]
- id = params[:project_id]
+ def upload_model_class
+ Project
+ end
- Project.find_by_full_path("#{namespace}/#{id}")
- end
+ def uploader_class
+ FileUploader
end
- alias_method :model, :project
+ def find_model
+ return @project if @project
+
+ namespace = params[:namespace_id]
+ id = params[:project_id]
+
+ Project.find_by_full_path("#{namespace}/#{id}")
+ end
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 16a74f82d3f..3d227b0a955 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -1,19 +1,34 @@
class UploadsController < ApplicationController
include UploadsActions
+ UnknownUploadModelError = Class.new(StandardError)
+
+ MODEL_CLASSES = {
+ "user" => User,
+ "project" => Project,
+ "note" => Note,
+ "group" => Group,
+ "appearance" => Appearance,
+ "personal_snippet" => PersonalSnippet,
+ nil => PersonalSnippet
+ }.freeze
+
+ rescue_from UnknownUploadModelError, with: :render_404
+
skip_before_action :authenticate_user!
+ before_action :upload_mount_satisfied?
before_action :find_model
before_action :authorize_access!, only: [:show]
before_action :authorize_create_access!, only: [:create]
- private
+ def uploader_class
+ PersonalFileUploader
+ end
def find_model
return nil unless params[:id]
- return render_404 unless upload_model && upload_mount
-
- @model = upload_model.find(params[:id])
+ upload_model_class.find(params[:id])
end
def authorize_access!
@@ -53,55 +68,17 @@ class UploadsController < ApplicationController
end
end
- def upload_model
- upload_models = {
- "user" => User,
- "project" => Project,
- "note" => Note,
- "group" => Group,
- "appearance" => Appearance,
- "personal_snippet" => PersonalSnippet
- }
-
- upload_models[params[:model]]
- end
-
- def upload_mount
- return true unless params[:mounted_as]
-
- upload_mounts = %w(avatar attachment file logo header_logo)
-
- if upload_mounts.include?(params[:mounted_as])
- params[:mounted_as]
- end
+ def upload_model_class
+ MODEL_CLASSES[params[:model]] || raise(UnknownUploadModelError)
end
- def uploader
- return @uploader if defined?(@uploader)
-
- case model
- when nil
- @uploader = PersonalFileUploader.new(nil, params[:secret])
-
- @uploader.retrieve_from_store!(params[:filename])
- when PersonalSnippet
- @uploader = PersonalFileUploader.new(model, params[:secret])
-
- @uploader.retrieve_from_store!(params[:filename])
- else
- @uploader = @model.public_send(upload_mount) # rubocop:disable GitlabSecurity/PublicSend
-
- redirect_to @uploader.url unless @uploader.file_storage?
- end
-
- @uploader
+ def upload_model_class_has_mounts?
+ upload_model_class < CarrierWave::Mount::Extension
end
- def uploader_class
- PersonalFileUploader
- end
+ def upload_mount_satisfied?
+ return true unless upload_model_class_has_mounts?
- def model
- @model ||= find_model
+ upload_model_class.uploader_options.has_key?(upload_mount)
end
end
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index 76cfe28742a..dcd14c08f3c 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -11,6 +11,7 @@ class Appearance < ActiveRecord::Base
mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader
+
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
CACHE_KEY = 'current_appearance'.freeze
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index 10659030910..d35e37935fb 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -1,6 +1,30 @@
module Avatarable
extend ActiveSupport::Concern
+ included do
+ prepend ShadowMethods
+
+ validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
+ validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
+
+ mount_uploader :avatar, AvatarUploader
+ end
+
+ module ShadowMethods
+ def avatar_url(**args)
+ # We use avatar_path instead of overriding avatar_url because of carrierwave.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
+
+ avatar_path(only_path: args.fetch(:only_path, true)) || super
+ end
+ end
+
+ def avatar_type
+ unless self.avatar.image?
+ self.errors.add :avatar, "only images allowed"
+ end
+ end
+
def avatar_path(only_path: true)
return unless self[:avatar].present?
diff --git a/app/models/group.rb b/app/models/group.rb
index fddace03387..415507a6aea 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -29,17 +29,14 @@ class Group < Namespace
has_many :variables, class_name: 'Ci::GroupVariable'
has_many :custom_attributes, class_name: 'GroupCustomAttribute'
- validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+
validate :visibility_level_allowed_by_projects
validate :visibility_level_allowed_by_sub_groups
validate :visibility_level_allowed_by_parent
- validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
-
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
- mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
after_create :post_create_hook
after_destroy :post_destroy_hook
@@ -116,12 +113,6 @@ class Group < Namespace
visibility_level_allowed_by_sub_groups?(level)
end
- def avatar_url(**args)
- # We use avatar_path instead of overriding avatar_url because of carrierwave.
- # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
- avatar_path(args)
- end
-
def lfs_enabled?
return false unless Gitlab.config.lfs.enabled
return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
diff --git a/app/models/note.rb b/app/models/note.rb
index 184fbd5f5ae..a84db8982e5 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -88,6 +88,7 @@ class Note < ActiveRecord::Base
end
end
+ # @deprecated attachments are handler by the MarkdownUploader
mount_uploader :attachment, AttachmentUploader
# Scopes
diff --git a/app/models/project.rb b/app/models/project.rb
index 4def590a7a9..90f5df6265d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -256,9 +256,6 @@ class Project < ActiveRecord::Base
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
- validate :avatar_type,
- if: ->(project) { project.avatar.present? && project.avatar_changed? }
- validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
validate :visibility_level_allowed_by_group
validate :visibility_level_allowed_as_fork
validate :check_wiki_path_conflict
@@ -266,7 +263,6 @@ class Project < ActiveRecord::Base
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
- mount_uploader :avatar, AvatarUploader
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Scopes
@@ -289,7 +285,6 @@ class Project < ActiveRecord::Base
scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
-
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
@@ -923,20 +918,12 @@ class Project < ActiveRecord::Base
issues_tracker.to_param == 'jira'
end
- def avatar_type
- unless self.avatar.image?
- self.errors.add :avatar, 'only images allowed'
- end
- end
-
def avatar_in_git
repository.avatar
end
def avatar_url(**args)
- # We use avatar_path instead of overriding avatar_url because of carrierwave.
- # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
- avatar_path(args) || (Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git)
+ Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git
end
# For compatibility with old code
diff --git a/app/models/upload.rb b/app/models/upload.rb
index f194d7bdb80..a9f144d2fa2 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -9,22 +9,11 @@ class Upload < ActiveRecord::Base
validates :model, presence: true
validates :uploader, presence: true
- before_save :calculate_checksum, if: :foreground_checksum?
- after_commit :schedule_checksum, unless: :foreground_checksum?
+ before_save :calculate_checksum!, if: :foreground_checksummable?
+ after_commit :schedule_checksum, if: :checksummable?
- def self.remove_path(path)
- where(path: path).destroy_all
- end
-
- def self.record(uploader)
- remove_path(uploader.relative_path)
-
- create(
- size: uploader.file.size,
- path: uploader.relative_path,
- model: uploader.model,
- uploader: uploader.class.to_s
- )
+ def self.hexdigest(path)
+ Digest::SHA256.file(path).hexdigest
end
def absolute_path
@@ -33,10 +22,18 @@ class Upload < ActiveRecord::Base
uploader_class.absolute_path(self)
end
- def calculate_checksum
- return unless exist?
+ def calculate_checksum!
+ self.checksum = nil
+ return unless checksummable?
- self.checksum = Digest::SHA256.file(absolute_path).hexdigest
+ self.checksum = self.class.hexdigest(absolute_path)
+ end
+
+ def build_uploader
+ uploader_class.new(model).tap do |uploader|
+ uploader.upload = self
+ uploader.retrieve_from_store!(identifier)
+ end
end
def exist?
@@ -45,8 +42,16 @@ class Upload < ActiveRecord::Base
private
- def foreground_checksum?
- size <= CHECKSUM_THRESHOLD
+ def checksummable?
+ checksum.nil? && local? && exist?
+ end
+
+ def local?
+ return true
+ end
+
+ def foreground_checksummable?
+ checksummable? && size <= CHECKSUM_THRESHOLD
end
def schedule_checksum
@@ -57,6 +62,10 @@ class Upload < ActiveRecord::Base
!path.start_with?('/')
end
+ def identifier
+ File.basename(path)
+ end
+
def uploader_class
Object.const_get(uploader)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index fb5d56a68b0..89e787c3274 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -137,6 +137,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
has_many :custom_attributes, class_name: 'UserCustomAttribute'
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
#
# Validations
@@ -159,12 +160,10 @@ class User < ActiveRecord::Base
validate :namespace_uniq, if: :username_changed?
validate :namespace_move_dir_allowed, if: :username_changed?
- validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
- validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed?
@@ -225,9 +224,6 @@ class User < ActiveRecord::Base
end
end
- mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
-
# Scopes
scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
@@ -527,12 +523,6 @@ class User < ActiveRecord::Base
end
end
- def avatar_type
- unless avatar.image?
- errors.add :avatar, "only images allowed"
- end
- end
-
def unique_email
if !emails.exists?(email: email) && Email.exists?(email: email)
errors.add(:email, 'has already been taken')
@@ -860,9 +850,7 @@ class User < ActiveRecord::Base
end
def avatar_url(size: nil, scale: 2, **args)
- # We use avatar_path instead of overriding avatar_url because of carrierwave.
- # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
- avatar_path(args) || GravatarService.new.execute(email, size, scale, username: username)
+ GravatarService.new.execute(email, size, scale, username: username)
end
def primary_email_verified?
diff --git a/app/services/projects/hashed_storage/migrate_attachments_service.rb b/app/services/projects/hashed_storage/migrate_attachments_service.rb
index f8aaec8a9c0..bc897d891d5 100644
--- a/app/services/projects/hashed_storage/migrate_attachments_service.rb
+++ b/app/services/projects/hashed_storage/migrate_attachments_service.rb
@@ -14,9 +14,9 @@ module Projects
@old_path = project.full_path
@new_path = project.disk_path
- origin = FileUploader.dynamic_path_segment(project)
+ origin = FileUploader.absolute_base_dir(project)
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:attachments]
- target = FileUploader.dynamic_path_segment(project)
+ target = FileUploader.absolute_base_dir(project)
result = move_folder!(origin, target)
project.save!
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index 109eb2fea0b..4930fb2fca7 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -1,10 +1,12 @@
class AttachmentUploader < GitlabUploader
- include RecordsUploads
include UploaderHelper
+ include RecordsUploads::Concern
storage :file
- def store_dir
- "#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ private
+
+ def dynamic_segment
+ File.join(model.class.to_s.underscore, mounted_as.to_s, model.id.to_s)
end
end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index cbb79376d5f..5c8e1cea62e 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -1,25 +1,24 @@
class AvatarUploader < GitlabUploader
- include RecordsUploads
include UploaderHelper
+ include RecordsUploads::Concern
storage :file
- def store_dir
- "#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
- end
-
def exists?
model.avatar.file && model.avatar.file.present?
end
- # We set move_to_store and move_to_cache to 'false' to prevent stealing
- # the avatar file from a project when forking it.
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/26158
- def move_to_store
+ def move_to_cache
false
end
- def move_to_cache
+ def move_to_store
false
end
+
+ private
+
+ def dynamic_segment
+ File.join(model.class.to_s.underscore, mounted_as.to_s, model.id.to_s)
+ end
end
diff --git a/app/uploaders/file_mover.rb b/app/uploaders/file_mover.rb
index 00c2888d224..e7af1483d23 100644
--- a/app/uploaders/file_mover.rb
+++ b/app/uploaders/file_mover.rb
@@ -21,7 +21,8 @@ class FileMover
end
def update_markdown
- updated_text = model.read_attribute(update_field).gsub(temp_file_uploader.to_markdown, uploader.to_markdown)
+ updated_text = model.read_attribute(update_field)
+ .gsub(temp_file_uploader.markdown_link, uploader.markdown_link)
model.update_attribute(update_field, updated_text)
true
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 0b591e3bbbb..85ae9863b13 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -1,23 +1,38 @@
+# This class breaks the actual CarrierWave concept.
+# Every uploader should use a base_dir that is model agnostic so we can build
+# back URLs from base_dir-relative paths saved in the `Upload` model.
+#
+# As the `.base_dir` is model dependent and **not** saved in the upload model (see #upload_path)
+# there is no way to build back the correct file path without the model, which defies
+# CarrierWave way of storing files.
+#
class FileUploader < GitlabUploader
- include RecordsUploads
include UploaderHelper
+ include RecordsUploads::Concern
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?[0-9a-f]{32})/(?.*?)\)}
+ DYNAMIC_PATH_PATTERN = %r{(?\h{32})/(?.*)}
storage :file
- def self.absolute_path(upload_record)
+ def self.root
+ File.join(options.storage_path, 'uploads')
+ end
+
+ def self.absolute_path(upload)
File.join(
- self.dynamic_path_segment(upload_record.model),
- upload_record.path
+ absolute_base_dir(upload.model),
+ upload.path # already contain the dynamic_segment, see #upload_path
)
end
- # Not using `GitlabUploader.base_dir` because all project namespaces are in
- # the `public/uploads` dir.
- #
- def self.base_dir
- root_dir
+ def self.base_dir(model)
+ model_path_segment(model)
+ end
+
+ # used in migrations and import/exports
+ def self.absolute_base_dir(model)
+ File.join(root, base_dir(model))
end
# Returns the part of `store_dir` that can change based on the model's current
@@ -29,63 +44,96 @@ class FileUploader < GitlabUploader
# model - Object that responds to `full_path` and `disk_path`
#
# Returns a String without a trailing slash
- def self.dynamic_path_segment(model)
+ def self.model_path_segment(model)
if model.hashed_storage?(:attachments)
- dynamic_path_builder(model.disk_path)
+ model.disk_path
else
- dynamic_path_builder(model.full_path)
+ model.full_path
end
end
- # Auxiliary method to build dynamic path segment when not using a project model
- #
- # Prefer to use the `.dynamic_path_segment` as it includes Hashed Storage specific logic
- def self.dynamic_path_builder(path)
- File.join(CarrierWave.root, base_dir, path)
+ def self.upload_path(secret, identifier)
+ File.join(secret, identifier)
+ end
+
+ def self.generate_secret
+ SecureRandom.hex
end
attr_accessor :model
- attr_reader :secret
def initialize(model, secret = nil)
@model = model
- @secret = secret || generate_secret
+ @secret = secret
end
- def store_dir
- File.join(dynamic_path_segment, @secret)
+ def base_dir
+ self.class.base_dir(@model)
end
- def relative_path
- self.file.path.sub("#{dynamic_path_segment}/", '')
+ # we don't need to know the actual path, an uploader instance should be
+ # able to yield the file content on demand, so we should build the digest
+ def absolute_path
+ self.class.absolute_path(@upload)
end
- def to_markdown
- to_h[:markdown]
+ def upload_path
+ self.class.upload_path(dynamic_segment, identifier)
end
- def to_h
- filename = image_or_video? ? self.file.basename : self.file.filename
- escaped_filename = filename.gsub("]", "\\]")
+ def model_path_segment
+ self.class.model_path_segment(@model)
+ end
+
+ def store_dir
+ File.join(base_dir, dynamic_segment)
+ end
- markdown = "[#{escaped_filename}](#{secure_url})"
+ def markdown_link
+ markdown = "[#{markdown_name}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous?
+ markdown
+ end
+ def to_h
{
- alt: filename,
+ alt: markdown_name,
url: secure_url,
- markdown: markdown
+ markdown: markdown_link
}
end
+ def filename
+ self.file.filename
+ end
+
+ # the upload does not hold the secret, but holds the path
+ # which contains the secret: extract it
+ def upload=(value)
+ if matches = DYNAMIC_PATH_PATTERN.match(value.path)
+ @secret = matches[:secret]
+ @identifier = matches[:identifier]
+ end
+
+ super
+ end
+
+ def secret
+ @secret ||= self.class.generate_secret
+ end
+
private
- def dynamic_path_segment
- self.class.dynamic_path_segment(model)
+ def markdown_name
+ (image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
end
- def generate_secret
- SecureRandom.hex
+ def identifier
+ @identifier ||= filename
+ end
+
+ def dynamic_segment
+ secret
end
def secure_url
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 7f72b3ce471..be8ab05b036 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -1,28 +1,32 @@
class GitlabUploader < CarrierWave::Uploader::Base
- def self.absolute_path(upload_record)
- File.join(CarrierWave.root, upload_record.path)
- end
+ class_attribute :options
- def self.root_dir
- 'uploads'
- end
+ class << self
+ # DSL setter
+ def storage_options(options)
+ self.options = options
+ end
- # When object storage is used, keep the `root_dir` as `base_dir`.
- # The files aren't really in folders there, they just have a name.
- # The files that contain user input in their name, also contain a hash, so
- # the names are still unique
- #
- # This method is overridden in the `FileUploader`
- def self.base_dir
- return root_dir unless file_storage?
+ def root
+ options.storage_path
+ end
- File.join(root_dir, '-', 'system')
- end
+ # represent the directory namespacing at the class level
+ def base_dir
+ options.fetch('base_dir', '')
+ end
- def self.file_storage?
- self.storage == CarrierWave::Storage::File
+ def file_storage?
+ storage == CarrierWave::Storage::File
+ end
+
+ def absolute_path(upload_record)
+ File.join(root, upload_record.path)
+ end
end
+ storage_options Gitlab.config.uploads
+
delegate :base_dir, :file_storage?, to: :class
def file_cache_storage?
@@ -31,34 +35,28 @@ class GitlabUploader < CarrierWave::Uploader::Base
# Reduce disk IO
def move_to_cache
- true
+ super || true
end
# Reduce disk IO
def move_to_store
- true
- end
-
- # Designed to be overridden by child uploaders that have a dynamic path
- # segment -- that is, a path that changes based on mutable attributes of its
- # associated model
- #
- # For example, `FileUploader` builds the storage path based on the associated
- # project model's `path_with_namespace` value, which can change when the
- # project or its containing namespace is moved or renamed.
- def relative_path
- self.file.path.sub("#{root}/", '')
+ super || true
end
def exists?
file.present?
end
- # Override this if you don't want to save files by default to the Rails.root directory
+ def store_dir
+ File.join(base_dir, dynamic_segment)
+ end
+
+ def cache_dir
+ File.join(root, base_dir, 'tmp/cache')
+ end
+
def work_dir
- # Default path set by CarrierWave:
- # https://github.com/carrierwaveuploader/carrierwave/blob/v1.0.0/lib/carrierwave/uploader/cache.rb#L182
- CarrierWave.tmp_path
+ File.join(root, base_dir, 'tmp/work')
end
def filename
@@ -67,6 +65,13 @@ class GitlabUploader < CarrierWave::Uploader::Base
private
+ # Designed to be overridden by child uploaders that have a dynamic path
+ # segment -- that is, a path that changes based on mutable attributes of its
+ # associated model
+ def dynamic_segment
+ raise(NotImplementedError)
+ end
+
# To prevent files from moving across filesystems, override the default
# implementation:
# http://github.com/carrierwaveuploader/carrierwave/blob/v1.0.0/lib/carrierwave/uploader/cache.rb#L181-L183
@@ -74,6 +79,6 @@ class GitlabUploader < CarrierWave::Uploader::Base
# To be safe, keep this directory outside of the the cache directory
# because calling CarrierWave.clean_cache_files! will remove any files in
# the cache directory.
- File.join(work_dir, @cache_id, version_name.to_s, for_file)
+ File.join(work_dir, cache_id, version_name.to_s, for_file)
end
end
diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb
index 15dfb5a5763..0abb462ab7d 100644
--- a/app/uploaders/job_artifact_uploader.rb
+++ b/app/uploaders/job_artifact_uploader.rb
@@ -1,13 +1,7 @@
class JobArtifactUploader < GitlabUploader
- storage :file
+ extend Workhorse::UploadPath
- def self.local_store_path
- Gitlab.config.artifacts.path
- end
-
- def self.artifacts_upload_path
- File.join(self.local_store_path, 'tmp/uploads/')
- end
+ storage_options Gitlab.config.artifacts
def size
return super if model.size.nil?
@@ -16,24 +10,12 @@ class JobArtifactUploader < GitlabUploader
end
def store_dir
- default_local_path
- end
-
- def cache_dir
- File.join(self.class.local_store_path, 'tmp/cache')
- end
-
- def work_dir
- File.join(self.class.local_store_path, 'tmp/work')
+ dynamic_segment
end
private
- def default_local_path
- File.join(self.class.local_store_path, default_path)
- end
-
- def default_path
+ def dynamic_segment
creation_date = model.created_at.utc.strftime('%Y_%m_%d')
File.join(disk_hash[0..1], disk_hash[2..3], disk_hash,
diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb
index 4f7f8a63108..28c458d3ff1 100644
--- a/app/uploaders/legacy_artifact_uploader.rb
+++ b/app/uploaders/legacy_artifact_uploader.rb
@@ -1,33 +1,15 @@
class LegacyArtifactUploader < GitlabUploader
- storage :file
+ extend Workhorse::UploadPath
- def self.local_store_path
- Gitlab.config.artifacts.path
- end
-
- def self.artifacts_upload_path
- File.join(self.local_store_path, 'tmp/uploads/')
- end
+ storage_options Gitlab.config.artifacts
def store_dir
- default_local_path
- end
-
- def cache_dir
- File.join(self.class.local_store_path, 'tmp/cache')
- end
-
- def work_dir
- File.join(self.class.local_store_path, 'tmp/work')
+ dynamic_segment
end
private
- def default_local_path
- File.join(self.class.local_store_path, default_path)
- end
-
- def default_path
+ def dynamic_segment
File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s)
end
end
diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb
index d11ebf0f9ca..e04c97ce179 100644
--- a/app/uploaders/lfs_object_uploader.rb
+++ b/app/uploaders/lfs_object_uploader.rb
@@ -1,19 +1,24 @@
class LfsObjectUploader < GitlabUploader
- storage :file
+ extend Workhorse::UploadPath
- def store_dir
- "#{Gitlab.config.lfs.storage_path}/#{model.oid[0, 2]}/#{model.oid[2, 2]}"
+ # LfsObject are in `tmp/upload` instead of `tmp/uploads`
+ def self.workhorse_upload_path
+ File.join(root, 'tmp/upload')
end
- def cache_dir
- "#{Gitlab.config.lfs.storage_path}/tmp/cache"
- end
+ storage_options Gitlab.config.lfs
def filename
model.oid[4..-1]
end
- def work_dir
- File.join(Gitlab.config.lfs.storage_path, 'tmp', 'work')
+ def store_dir
+ dynamic_segment
+ end
+
+ private
+
+ def dynamic_segment
+ File.join(model.oid[0, 2], model.oid[2, 2])
end
end
diff --git a/app/uploaders/namespace_file_uploader.rb b/app/uploaders/namespace_file_uploader.rb
index 672126e9ec2..993e85fbc13 100644
--- a/app/uploaders/namespace_file_uploader.rb
+++ b/app/uploaders/namespace_file_uploader.rb
@@ -1,15 +1,19 @@
class NamespaceFileUploader < FileUploader
- def self.base_dir
- File.join(root_dir, '-', 'system', 'namespace')
+ # Re-Override
+ def self.root
+ options.storage_path
end
- def self.dynamic_path_segment(model)
- dynamic_path_builder(model.id.to_s)
+ def self.base_dir(model)
+ File.join(options.base_dir, 'namespace', model_path_segment(model))
end
- private
+ def self.model_path_segment(model)
+ File.join(model.id.to_s)
+ end
- def secure_url
- File.join('/uploads', @secret, file.filename)
+ # Re-Override
+ def store_dir
+ File.join(base_dir, dynamic_segment)
end
end
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index 3298ad104ec..e7d9ecd3222 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -1,23 +1,27 @@
class PersonalFileUploader < FileUploader
- def self.dynamic_path_segment(model)
- File.join(CarrierWave.root, model_path(model))
+ # Re-Override
+ def self.root
+ options.storage_path
end
- def self.base_dir
- File.join(root_dir, '-', 'system')
+ def self.base_dir(model)
+ File.join(options.base_dir, model_path_segment(model))
end
- private
+ def self.model_path_segment(model)
+ return 'temp/' unless model
- def secure_url
- File.join(self.class.model_path(model), secret, file.filename)
+ File.join(model.class.to_s.underscore, model.id.to_s)
+ end
+
+ # Revert-Override
+ def store_dir
+ File.join(base_dir, dynamic_segment)
end
- def self.model_path(model)
- if model
- File.join("/#{base_dir}", model.class.to_s.underscore, model.id.to_s)
- else
- File.join("/#{base_dir}", 'temp')
- end
+ private
+
+ def secure_url
+ File.join('/', base_dir, secret, file.filename)
end
end
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index feb4f04d7b7..dfb8dccec57 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -1,35 +1,61 @@
module RecordsUploads
- extend ActiveSupport::Concern
+ module Concern
+ extend ActiveSupport::Concern
- included do
- after :store, :record_upload
- before :remove, :destroy_upload
- end
+ attr_accessor :upload
- # After storing an attachment, create a corresponding Upload record
- #
- # NOTE: We're ignoring the argument passed to this callback because we want
- # the `SanitizedFile` object from `CarrierWave::Uploader::Base#file`, not the
- # `Tempfile` object the callback gets.
- #
- # Called `after :store`
- def record_upload(_tempfile = nil)
- return unless model
- return unless file_storage?
- return unless file.exists?
-
- Upload.record(self)
- end
+ included do
+ after :store, :record_upload
+ before :remove, :destroy_upload
+ end
+
+ # After storing an attachment, create a corresponding Upload record
+ #
+ # NOTE: We're ignoring the argument passed to this callback because we want
+ # the `SanitizedFile` object from `CarrierWave::Uploader::Base#file`, not the
+ # `Tempfile` object the callback gets.
+ #
+ # Called `after :store`
+ def record_upload(_tempfile = nil)
+ return unless model
+ return unless file && file.exists?
+
+ Upload.transaction do
+ uploads.where(path: upload_path).delete_all
+ upload.destroy! if upload
+
+ self.upload = build_upload_from_uploader(self)
+ upload.save!
+ end
+ end
+
+ def upload_path
+ File.join(store_dir, filename.to_s)
+ end
+
+ private
+
+ def uploads
+ Upload.order(id: :desc).where(uploader: self.class.to_s)
+ end
- private
+ def build_upload_from_uploader(uploader)
+ Upload.new(
+ size: uploader.file.size,
+ path: uploader.upload_path,
+ model: uploader.model,
+ uploader: uploader.class.to_s
+ )
+ end
- # Before removing an attachment, destroy any Upload records at the same path
- #
- # Called `before :remove`
- def destroy_upload(*args)
- return unless file_storage?
- return unless file
+ # Before removing an attachment, destroy any Upload records at the same path
+ #
+ # Called `before :remove`
+ def destroy_upload(*args)
+ return unless file && file.exists?
- Upload.remove_path(relative_path)
+ self.upload = nil
+ uploads.where(path: upload_path).delete_all
+ end
end
end
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index 7635c20ab3a..fd446d31092 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -32,14 +32,7 @@ module UploaderHelper
def extension_match?(extensions)
return false unless file
- extension =
- if file.respond_to?(:extension)
- file.extension
- else
- # Not all CarrierWave storages respond to :extension
- File.extname(file.path).delete('.')
- end
-
+ extension = file.try(:extension) || File.extname(file.path).delete('.')
extensions.include?(extension.downcase)
end
end
diff --git a/app/uploaders/workhorse.rb b/app/uploaders/workhorse.rb
new file mode 100644
index 00000000000..782032cf516
--- /dev/null
+++ b/app/uploaders/workhorse.rb
@@ -0,0 +1,7 @@
+module Workhorse
+ module UploadPath
+ def workhorse_upload_path
+ File.join(root, base_dir, 'tmp/uploads')
+ end
+ end
+end
diff --git a/app/workers/upload_checksum_worker.rb b/app/workers/upload_checksum_worker.rb
index 9222760c031..65d40336f18 100644
--- a/app/workers/upload_checksum_worker.rb
+++ b/app/workers/upload_checksum_worker.rb
@@ -3,7 +3,7 @@ class UploadChecksumWorker
def perform(upload_id)
upload = Upload.find(upload_id)
- upload.calculate_checksum
+ upload.calculate_checksum!
upload.save!
rescue ActiveRecord::RecordNotFound
Rails.logger.error("UploadChecksumWorker: couldn't find upload #{upload_id}, skipping")
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 25f4085deb2..33230b9355d 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -152,6 +152,12 @@ production: &base
# The location where LFS objects are stored (default: shared/lfs-objects).
# storage_path: shared/lfs-objects
+ ## Uploads (attachments, avatars, etc...)
+ uploads:
+ # The location where uploads objects are stored (default: public/).
+ # storage_path: public/
+ # base_dir: uploads/-/system
+
## GitLab Pages
pages:
enabled: false
@@ -644,6 +650,8 @@ test:
enabled: false
artifacts:
path: tmp/tests/artifacts
+ uploads:
+ storage_path: tmp/tests/public
gitlab:
host: localhost
port: 80
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 5b4e6b5db88..5ad46d47cb6 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -300,8 +300,10 @@ Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled']
#
Settings['artifacts'] ||= Settingslogic.new({})
Settings.artifacts['enabled'] = true if Settings.artifacts['enabled'].nil?
-Settings.artifacts['path'] = Settings.absolute(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"))
-Settings.artifacts['max_size'] ||= 100 # in megabytes
+Settings.artifacts['storage_path'] = Settings.absolute(Settings.artifacts.values_at('path', 'storage_path').compact.first || File.join(Settings.shared['path'], "artifacts"))
+# Settings.artifact['path'] is deprecated, use `storage_path` instead
+Settings.artifacts['path'] = Settings.artifacts['storage_path']
+Settings.artifacts['max_size'] ||= 100 # in megabytes
#
# Registry
@@ -338,6 +340,13 @@ Settings['lfs'] ||= Settingslogic.new({})
Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil?
Settings.lfs['storage_path'] = Settings.absolute(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"))
+#
+# Uploads
+#
+Settings['uploads'] ||= Settingslogic.new({})
+Settings.uploads['storage_path'] = Settings.absolute(Settings.uploads['storage_path'] || 'public')
+Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system'
+
#
# Mattermost
#
diff --git a/db/migrate/20180119135717_add_uploader_index_to_uploads.rb b/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
new file mode 100644
index 00000000000..a678c3d049f
--- /dev/null
+++ b/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
@@ -0,0 +1,20 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddUploaderIndexToUploads < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :uploads, :path
+ add_concurrent_index :uploads, [:uploader, :path], using: :btree
+ end
+
+ def down
+ remove_concurrent_index :uploads, [:uploader, :path]
+ add_concurrent_index :uploads, :path, using: :btree
+ end
+end
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index cf00e24e11a..76354b92820 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -14,8 +14,8 @@ There are many places where file uploading is used, according to contexts:
- User snippet attachments
* Project
- Project avatars
- - Issues/MR Markdown attachments
- - Issues/MR Legacy Markdown attachments
+ - Issues/MR/Notes Markdown attachments
+ - Issues/MR/Notes Legacy Markdown attachments
- CI Build Artifacts
- LFS Objects
@@ -25,7 +25,7 @@ There are many places where file uploading is used, according to contexts:
GitLab started saving everything on local disk. While directory location changed from previous versions,
they are still not 100% standardized. You can see them below:
-| Description | In DB? | Relative path | Uploader class | model_type |
+| Description | In DB? | Relative path (from CarrierWave.root) | Uploader class | model_type |
| ------------------------------------- | ------ | ----------------------------------------------------------- | ---------------------- | ---------- |
| Instance logo | yes | uploads/-/system/appearance/logo/:id/:filename | `AttachmentUploader` | Appearance |
| Header logo | yes | uploads/-/system/appearance/header_logo/:id/:filename | `AttachmentUploader` | Appearance |
@@ -33,17 +33,107 @@ they are still not 100% standardized. You can see them below:
| User avatars | yes | uploads/-/system/user/avatar/:id/:filename | `AvatarUploader` | User |
| User snippet attachments | yes | uploads/-/system/personal_snippet/:id/:random_hex/:filename | `PersonalFileUploader` | Snippet |
| Project avatars | yes | uploads/-/system/project/avatar/:id/:filename | `AvatarUploader` | Project |
-| Issues/MR Markdown attachments | yes | uploads/:project_path_with_namespace/:random_hex/:filename | `FileUploader` | Project |
-| Issues/MR Legacy Markdown attachments | no | uploads/-/system/note/attachment/:id/:filename | `AttachmentUploader` | Note |
+| Issues/MR/Notes Markdown attachments | yes | uploads/:project_path_with_namespace/:random_hex/:filename | `FileUploader` | Project |
+| Issues/MR/Notes Legacy Markdown attachments | no | uploads/-/system/note/attachment/:id/:filename | `AttachmentUploader` | Note |
| CI Artifacts (CE) | yes | shared/artifacts/:year_:month/:project_id/:id | `ArtifactUploader` | Ci::Build |
| LFS Objects (CE) | yes | shared/lfs-objects/:hex/:hex/:object_hash | `LfsObjectUploader` | LfsObject |
CI Artifacts and LFS Objects behave differently in CE and EE. In CE they inherit the `GitlabUploader`
-while in EE they inherit the `ObjectStoreUploader` and store files in and S3 API compatible object store.
+while in EE they inherit the `ObjectStorage` and store files in and S3 API compatible object store.
-In the case of Issues/MR Markdown attachments, there is a different approach using the [Hashed Storage] layout,
+In the case of Issues/MR/Notes Markdown attachments, there is a different approach using the [Hashed Storage] layout,
instead of basing the path into a mutable variable `:project_path_with_namespace`, it's possible to use the
hash of the project ID instead, if project migrates to the new approach (introduced in 10.2).
+### Path segments
+
+Files are stored at multiple locations and use different path schemes.
+All the `GitlabUploader` derived classes should comply with this path segment schema:
+
+```
+| GitlabUploader
+| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
+| `/public/` | `uploads/-/system/` | `user/avatar/:id/` | `:filename` |
+| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
+| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
+| | `CarrierWave::Uploader#store_dir` | |
+
+| FileUploader
+| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
+| `/shared/` | `artifacts/` | `:year_:month/:id` | `:filename` |
+| `/shared/` | `snippets/` | `:secret/` | `:filename` |
+| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
+| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
+| | `CarrierWave::Uploader#store_dir` | |
+| | | `FileUploader#upload_path |
+
+| ObjectStore::Concern (store = remote)
+| ----------------------- + ------------------------- + ----------------------------------- + -------------------------------- |
+| `` | | `user/avatar/:id/` | `:filename` |
+| ----------------------- + ------------------------- + ----------------------------------- + -------------------------------- |
+| `#fog_dir` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
+| | | `ObjectStorage::Concern#store_dir` | |
+| | | `ObjectStorage::Concern#upload_path |
+```
+
+The `RecordsUploads::Concern` concern will create an `Upload` entry for every file stored by a `GitlabUploader` persisting the dynamic parts of the path using
+`GitlabUploader#dynamic_path`. You may then use the `Upload#build_uploader` method to manipulate the file.
+
+## Object Storage
+
+By including the `ObjectStorage::Concern` in the `GitlabUploader` derived class, you may enable the object storage for this uploader. To enable the object storage
+in your uploader, you need to either 1) include `RecordsUpload::Concern` and prepend `ObjectStorage::Extension::RecordsUploads` or 2) mount the uploader and create a new field named `_store`.
+
+The `CarrierWave::Uploader#store_dir` is overriden to
+
+ - `GitlabUploader.base_dir` + `GitlabUploader.dynamic_segment` when the store is LOCAL
+ - `GitlabUploader.dynamic_segment` when the store is REMOTE (the bucket name is used to namespace)
+
+### Using `ObjectStorage::Extension::RecordsUploads`
+
+> Note: this concern will automatically include `RecordsUploads::Concern` if not already included.
+
+The `ObjectStorage::Concern` uploader will search for the matching `Upload` to select the correct object store. The `Upload` is mapped using `#store_dirs + identifier` for each store (LOCAL/REMOTE).
+
+```ruby
+class SongUploader < GitlabUploader
+ include RecordsUploads::Concern
+ include ObjectStorage::Concern
+ prepend ObjectStorage::Extension::RecordsUploads
+
+ ...
+end
+
+class Thing < ActiveRecord::Base
+ mount :theme, SongUploader # we have a great theme song!
+
+ ...
+end
+```
+
+### Using a mounted uploader
+
+The `ObjectStorage::Concern` will query the `model._store` attribute to select the correct object store.
+This column must be present in the model schema.
+
+```ruby
+class SongUploader < GitlabUploader
+ include ObjectStorage::Concern
+
+ ...
+end
+
+class Thing < ActiveRecord::Base
+ attr_reader :theme_store # this is an ActiveRecord attribute
+ mount :theme, SongUploader # we have a great theme song!
+
+ def theme_store
+ super || ObjectStorage::Store::LOCAL
+ end
+
+ ...
+end
+```
+
[CarrierWave]: https://github.com/carrierwaveuploader/carrierwave
[Hashed Storage]: ../administration/repository_storage_types.md
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 80feb629d54..1f80646a2ea 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -215,9 +215,9 @@ module API
job = authenticate_job!
forbidden!('Job is not running!') unless job.running?
- artifacts_upload_path = JobArtifactUploader.artifacts_upload_path
- artifacts = uploaded_file(:file, artifacts_upload_path)
- metadata = uploaded_file(:metadata, artifacts_upload_path)
+ workhorse_upload_path = JobArtifactUploader.workhorse_upload_path
+ artifacts = uploaded_file(:file, workhorse_upload_path)
+ metadata = uploaded_file(:metadata, workhorse_upload_path)
bad_request!('Missing artifacts file!') unless artifacts
file_to_large! unless artifacts.size < max_artifacts_size
diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb
index 7a582a20056..4383124d150 100644
--- a/lib/backup/artifacts.rb
+++ b/lib/backup/artifacts.rb
@@ -3,7 +3,7 @@ require 'backup/files'
module Backup
class Artifacts < Files
def initialize
- super('artifacts', LegacyArtifactUploader.local_store_path)
+ super('artifacts', JobArtifactUploader.root)
end
def create_files_dir
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb
index d60e41d9f9d..8a8e770940e 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb
@@ -143,7 +143,7 @@ module Gitlab
end
def absolute_path
- File.join(CarrierWave.root, path)
+ File.join(Gitlab.config.uploads.storage_path, path)
end
end
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
index 4e0121ca34d..a7a1bbe1752 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -11,9 +11,12 @@ module Gitlab
FIND_BATCH_SIZE = 500
RELATIVE_UPLOAD_DIR = "uploads".freeze
- ABSOLUTE_UPLOAD_DIR = "#{CarrierWave.root}/#{RELATIVE_UPLOAD_DIR}".freeze
+ ABSOLUTE_UPLOAD_DIR = File.join(
+ Gitlab.config.uploads.storage_path,
+ RELATIVE_UPLOAD_DIR
+ )
FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads'.freeze
- START_WITH_CARRIERWAVE_ROOT_REGEX = %r{\A#{CarrierWave.root}/}
+ START_WITH_ROOT_REGEX = %r{\A#{Gitlab.config.uploads.storage_path}/}
EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze
EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze
@@ -81,7 +84,7 @@ module Gitlab
paths = []
stdout.each_line("\0") do |line|
- paths << line.chomp("\0").sub(START_WITH_CARRIERWAVE_ROOT_REGEX, '')
+ paths << line.chomp("\0").sub(START_WITH_ROOT_REGEX, '')
if paths.size >= batch_size
yield(paths)
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
index 8fab5489616..3fdc3c27f73 100644
--- a/lib/gitlab/gfm/uploads_rewriter.rb
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -27,7 +27,7 @@ module Gitlab
with_link_in_tmp_dir(file.file) do |open_tmp_file|
new_uploader.store!(open_tmp_file)
end
- new_uploader.to_markdown
+ new_uploader.markdown_link
end
end
diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb
index 627a487d577..2f08dda55fd 100644
--- a/lib/gitlab/import_export/uploads_saver.rb
+++ b/lib/gitlab/import_export/uploads_saver.rb
@@ -17,15 +17,13 @@ module Gitlab
false
end
- private
+ def uploads_path
+ FileUploader.absolute_base_dir(@project)
+ end
def uploads_export_path
File.join(@shared.export_path, 'uploads')
end
-
- def uploads_path
- FileUploader.dynamic_path_segment(@project)
- end
end
end
end
diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb
index b5f41240529..7d7400bdabf 100644
--- a/lib/gitlab/uploads_transfer.rb
+++ b/lib/gitlab/uploads_transfer.rb
@@ -1,7 +1,7 @@
module Gitlab
class UploadsTransfer < ProjectTransfer
def root_dir
- File.join(CarrierWave.root, FileUploader.base_dir)
+ FileUploader.root
end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 633da44b22d..b3f8b0d174d 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -55,14 +55,14 @@ module Gitlab
def lfs_upload_ok(oid, size)
{
- StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
+ StoreLFSPath: LfsObjectUploader.workhorse_upload_path,
LfsOid: oid,
LfsSize: size
}
end
def artifact_upload_ok
- { TempPath: JobArtifactUploader.artifacts_upload_path }
+ { TempPath: JobArtifactUploader.workhorse_upload_path }
end
def send_git_blob(repository, blob)
@@ -147,8 +147,11 @@ module Gitlab
end
def send_artifacts_entry(build, entry)
+ file = build.artifacts_file
+ archive = file.file_storage? ? file.path : file.url
+
params = {
- 'Archive' => build.artifacts_file.path,
+ 'Archive' => archive,
'Entry' => Base64.encode64(entry.to_s)
}
diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb
index 67a11e56e94..6a1869d1a48 100644
--- a/spec/controllers/groups/uploads_controller_spec.rb
+++ b/spec/controllers/groups/uploads_controller_spec.rb
@@ -6,5 +6,7 @@ describe Groups::UploadsController do
{ group_id: model }
end
- it_behaves_like 'handle uploads'
+ it_behaves_like 'handle uploads' do
+ let(:uploader_class) { NamespaceFileUploader }
+ end
end
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index 12cb7b2647f..25a2e13fe1a 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -145,8 +145,7 @@ describe Projects::ArtifactsController do
context 'when using local file storage' do
it_behaves_like 'a valid file' do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
- let(:store) { ObjectStoreUploader::LOCAL_STORE }
- let(:archive_path) { JobArtifactUploader.local_store_path }
+ let(:archive_path) { JobArtifactUploader.root }
end
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 3a0c3faa7b4..b7df42168e0 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -53,7 +53,7 @@ describe Projects::RawController do
end
it 'serves the file' do
- expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: 'lfs_object.iso', disposition: 'attachment')
+ expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: 'lfs_object.iso', disposition: 'attachment')
get(:show,
namespace_id: public_project.namespace.to_param,
project_id: public_project,
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index b1f601a19e5..376b229ffc9 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -180,6 +180,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+
response
end
end
@@ -196,6 +197,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+
response
end
end
@@ -220,6 +222,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+
response
end
end
@@ -239,6 +242,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+
response
end
end
@@ -291,6 +295,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+
response
end
end
@@ -322,6 +327,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+
response
end
end
@@ -341,6 +347,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+
response
end
end
@@ -384,6 +391,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+
response
end
end
@@ -420,6 +428,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+
response
end
end
@@ -439,6 +448,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+
response
end
end
@@ -491,6 +501,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+
response
end
end
@@ -522,6 +533,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png'
+
response
end
end
@@ -541,6 +553,7 @@ describe UploadsController do
it_behaves_like 'content not cached without revalidation' do
subject do
get :show, model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png'
+
response
end
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 2defb4935ad..3f4e408b3a6 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -122,11 +122,11 @@ FactoryBot.define do
end
trait :with_attachment do
- attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") }
+ attachment { fixture_file_upload(Rails.root.join( "spec/fixtures/dk.png"), "image/png") }
end
trait :with_svg_attachment do
- attachment { fixture_file_upload(Rails.root + "spec/fixtures/unsanitized.svg", "image/svg+xml") }
+ attachment { fixture_file_upload(Rails.root.join("spec/fixtures/unsanitized.svg"), "image/svg+xml") }
end
transient do
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index c39500faea1..c8cfe251d27 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -1,24 +1,42 @@
FactoryBot.define do
factory :upload do
model { build(:project) }
- path { "uploads/-/system/project/avatar/avatar.jpg" }
size 100.kilobytes
uploader "AvatarUploader"
- trait :personal_snippet do
+ # we should build a mount agnostic upload by default
+ transient do
+ mounted_as :avatar
+ secret SecureRandom.hex
+ end
+
+ # this needs to comply with RecordsUpload::Concern#upload_path
+ path { File.join("uploads/-/system", model.class.to_s.underscore, mounted_as.to_s, 'avatar.jpg') }
+
+ trait :personal_snippet_upload do
model { build(:personal_snippet) }
+ path { File.join(secret, 'myfile.jpg') }
uploader "PersonalFileUploader"
end
trait :issuable_upload do
- path { "#{SecureRandom.hex}/myfile.jpg" }
+ path { File.join(secret, 'myfile.jpg') }
uploader "FileUploader"
end
trait :namespace_upload do
- path { "#{SecureRandom.hex}/myfile.jpg" }
model { build(:group) }
+ path { File.join(secret, 'myfile.jpg') }
uploader "NamespaceFileUploader"
end
+
+ trait :attachment_upload do
+ transient do
+ mounted_as :attachment
+ end
+
+ model { build(:note) }
+ uploader "AttachmentUploader"
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
index 8bb9ebe0419..1a4b9f427d6 100644
--- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
@@ -23,6 +23,27 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do
end
end
+ # E.g. The installation is in use at the time of migration, and someone has
+ # just uploaded a file
+ shared_examples 'does not add files in /uploads/tmp' do
+ let(:tmp_file) { Rails.root.join(described_class::ABSOLUTE_UPLOAD_DIR, 'tmp', 'some_file.jpg') }
+
+ before do
+ FileUtils.mkdir(File.dirname(tmp_file))
+ FileUtils.touch(tmp_file)
+ end
+
+ after do
+ FileUtils.rm(tmp_file)
+ end
+
+ it 'does not add files from /uploads/tmp' do
+ described_class.new.perform
+
+ expect(untracked_files_for_uploads.count).to eq(5)
+ end
+ end
+
it 'ensures the untracked_files_for_uploads table exists' do
expect do
described_class.new.perform
@@ -109,20 +130,8 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do
end
end
- # E.g. The installation is in use at the time of migration, and someone has
- # just uploaded a file
context 'when there are files in /uploads/tmp' do
- let(:tmp_file) { Rails.root.join(described_class::ABSOLUTE_UPLOAD_DIR, 'tmp', 'some_file.jpg') }
-
- before do
- FileUtils.touch(tmp_file)
- end
-
- after do
- FileUtils.rm(tmp_file)
- end
-
- it 'does not add files from /uploads/tmp' do
+ it_behaves_like 'does not add files in /uploads/tmp'
described_class.new.perform
expect(untracked_files_for_uploads.count).to eq(5)
@@ -197,24 +206,8 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do
end
end
- # E.g. The installation is in use at the time of migration, and someone has
- # just uploaded a file
context 'when there are files in /uploads/tmp' do
- let(:tmp_file) { Rails.root.join(described_class::ABSOLUTE_UPLOAD_DIR, 'tmp', 'some_file.jpg') }
-
- before do
- FileUtils.touch(tmp_file)
- end
-
- after do
- FileUtils.rm(tmp_file)
- end
-
- it 'does not add files from /uploads/tmp' do
- described_class.new.perform
-
- expect(untracked_files_for_uploads.count).to eq(5)
- end
+ it_behaves_like 'does not add files in /uploads/tmp'
end
end
end
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index 39e3b875c49..326ed2f2ecf 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -17,7 +17,7 @@ describe Gitlab::Gfm::UploadsRewriter do
end
let(:text) do
- "Text and #{image_uploader.to_markdown} and #{zip_uploader.to_markdown}"
+ "Text and #{image_uploader.markdown_link} and #{zip_uploader.markdown_link}"
end
describe '#rewrite' do
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index 63992ea8ab8..a685521cbf0 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -4,7 +4,6 @@ describe Gitlab::ImportExport::UploadsRestorer do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
- let(:uploads_path) { FileUploader.dynamic_path_segment(project) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
@@ -26,9 +25,9 @@ describe Gitlab::ImportExport::UploadsRestorer do
end
it 'copies the uploads to the project path' do
- restorer.restore
+ subject.restore
- uploads = Dir.glob(File.join(uploads_path, '**/*')).map { |file| File.basename(file) }
+ uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) }
expect(uploads).to include('dummy.txt')
end
@@ -44,9 +43,9 @@ describe Gitlab::ImportExport::UploadsRestorer do
end
it 'copies the uploads to the project path' do
- restorer.restore
+ subject.restore
- uploads = Dir.glob(File.join(uploads_path, '**/*')).map { |file| File.basename(file) }
+ uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) }
expect(uploads).to include('dummy.txt')
end
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index e8948de1f3a..959779523f4 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::ImportExport::UploadsSaver do
it 'copies the uploads to the export path' do
saver.save
- uploads = Dir.glob(File.join(shared.export_path, 'uploads', '**/*')).map { |file| File.basename(file) }
+ uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) }
expect(uploads).to include('banana_sample.gif')
end
@@ -52,7 +52,7 @@ describe Gitlab::ImportExport::UploadsSaver do
it 'copies the uploads to the export path' do
saver.save
- uploads = Dir.glob(File.join(shared.export_path, 'uploads', '**/*')).map { |file| File.basename(file) }
+ uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) }
expect(uploads).to include('banana_sample.gif')
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index c3673a0e2a3..6b7dbad128c 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -204,7 +204,7 @@ describe Namespace do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child, skip_disk_validation: true) }
- let(:uploads_dir) { File.join(CarrierWave.root, FileUploader.base_dir) }
+ let(:uploads_dir) { FileUploader.root }
let(:pages_dir) { File.join(TestEnv.pages_path) }
before do
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 345382ea8c7..42f3d609770 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -45,51 +45,6 @@ describe Upload do
end
end
- describe '.remove_path' do
- it 'removes all records at the given path' do
- described_class.create!(
- size: File.size(__FILE__),
- path: __FILE__,
- model: build_stubbed(:user),
- uploader: 'AvatarUploader'
- )
-
- expect { described_class.remove_path(__FILE__) }
- .to change { described_class.count }.from(1).to(0)
- end
- end
-
- describe '.record' do
- let(:fake_uploader) do
- double(
- file: double(size: 12_345),
- relative_path: 'foo/bar.jpg',
- model: build_stubbed(:user),
- class: 'AvatarUploader'
- )
- end
-
- it 'removes existing paths before creation' do
- expect(described_class).to receive(:remove_path)
- .with(fake_uploader.relative_path)
-
- described_class.record(fake_uploader)
- end
-
- it 'creates a new record and assigns size, path, model, and uploader' do
- upload = described_class.record(fake_uploader)
-
- aggregate_failures do
- expect(upload).to be_persisted
- expect(upload.size).to eq fake_uploader.file.size
- expect(upload.path).to eq fake_uploader.relative_path
- expect(upload.model_id).to eq fake_uploader.model.id
- expect(upload.model_type).to eq fake_uploader.model.class.to_s
- expect(upload.uploader).to eq fake_uploader.class
- end
- end
- end
-
describe '#absolute_path' do
it 'returns the path directly when already absolute' do
path = '/path/to/namespace/project/secret/file.jpg'
@@ -111,27 +66,27 @@ describe Upload do
end
end
- describe '#calculate_checksum' do
- it 'calculates the SHA256 sum' do
- upload = described_class.new(
- path: __FILE__,
- size: described_class::CHECKSUM_THRESHOLD - 1.megabyte
- )
+ describe '#calculate_checksum!' do
+ let(:upload) do
+ described_class.new(path: __FILE__,
+ size: described_class::CHECKSUM_THRESHOLD - 1.megabyte)
+ end
+
+ it 'sets `checksum` to SHA256 sum of the file' do
expected = Digest::SHA256.file(__FILE__).hexdigest
- expect { upload.calculate_checksum }
+ expect { upload.calculate_checksum! }
.to change { upload.checksum }.from(nil).to(expected)
end
- it 'returns nil for a non-existant file' do
- upload = described_class.new(
- path: __FILE__,
- size: described_class::CHECKSUM_THRESHOLD - 1.megabyte
- )
-
+ it 'sets `checksum` to nil for a non-existant file' do
expect(upload).to receive(:exist?).and_return(false)
- expect(upload.calculate_checksum).to be_nil
+ checksum = Digest::SHA256.file(__FILE__).hexdigest
+ upload.checksum = checksum
+
+ expect { upload.calculate_checksum! }
+ .to change { upload.checksum }.from(checksum).to(nil)
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index cb66d23b77c..c5c0b0c2867 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -945,7 +945,7 @@ describe API::Runner do
context 'when artifacts are being stored inside of tmp path' do
before do
# by configuring this path we allow to pass temp file from any path
- allow(JobArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
+ allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return('/')
end
context 'when job has been erased' do
@@ -1122,7 +1122,7 @@ describe API::Runner do
# by configuring this path we allow to pass file from @tmpdir only
# but all temporary files are stored in system tmp directory
@tmpdir = Dir.mktmpdir
- allow(JobArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
+ allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return(@tmpdir)
end
after do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index bee918a20aa..930ef49b7f3 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -958,7 +958,7 @@ describe 'Git LFS API and storage' do
end
it 'responds with status 200, location of lfs store and object details' do
- expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
+ expect(json_response['StoreLFSPath']).to eq(LfsObjectUploader.workhorse_upload_path)
expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size)
end
@@ -1075,7 +1075,7 @@ describe 'Git LFS API and storage' do
end
it 'with location of lfs store and object details' do
- expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
+ expect(json_response['StoreLFSPath']).to eq(LfsObjectUploader.workhorse_upload_path)
expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size)
end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 388c9d63c7b..322c91065e7 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -6,7 +6,7 @@ describe Issues::MoveService do
let(:title) { 'Some issue' }
let(:description) { 'Some issue description' }
let(:old_project) { create(:project) }
- let(:new_project) { create(:project) }
+ let(:new_project) { create(:project, group: create(:group)) }
let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') }
let(:old_issue) do
@@ -250,7 +250,7 @@ describe Issues::MoveService do
context 'issue description with uploads' do
let(:uploader) { build(:file_uploader, project: old_project) }
- let(:description) { "Text and #{uploader.to_markdown}" }
+ let(:description) { "Text and #{uploader.markdown_link}" }
include_context 'issue move executed'
diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
index 50e59954f73..15699574b3a 100644
--- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
@@ -6,7 +6,7 @@ describe Projects::HashedStorage::MigrateAttachmentsService do
let(:legacy_storage) { Storage::LegacyProject.new(project) }
let(:hashed_storage) { Storage::HashedProject.new(project) }
- let!(:upload) { Upload.find_by(path: file_uploader.relative_path) }
+ let!(:upload) { Upload.find_by(path: file_uploader.upload_path) }
let(:file_uploader) { build(:file_uploader, project: project) }
let(:old_path) { File.join(base_path(legacy_storage), upload.path) }
let(:new_path) { File.join(base_path(hashed_storage), upload.path) }
@@ -58,6 +58,6 @@ describe Projects::HashedStorage::MigrateAttachmentsService do
end
def base_path(storage)
- FileUploader.dynamic_path_builder(storage.disk_path)
+ File.join(FileUploader.root, storage.disk_path)
end
end
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 935c08221e0..7ce80c82439 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -2,6 +2,8 @@ shared_examples 'handle uploads' do
let(:user) { create(:user) }
let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ let(:secret) { FileUploader.generate_secret }
+ let(:uploader_class) { FileUploader }
describe "POST #create" do
context 'when a user is not authorized to upload a file' do
@@ -65,7 +67,12 @@ shared_examples 'handle uploads' do
describe "GET #show" do
let(:show_upload) do
- get :show, params.merge(secret: "123456", filename: "image.jpg")
+ get :show, params.merge(secret: secret, filename: "rails_sample.jpg")
+ end
+
+ before do
+ expect(FileUploader).to receive(:generate_secret).and_return(secret)
+ UploadService.new(model, jpg, uploader_class).execute
end
context "when the model is public" do
@@ -75,11 +82,6 @@ shared_examples 'handle uploads' do
context "when not signed in" do
context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
it "responds with status 200" do
show_upload
@@ -88,6 +90,10 @@ shared_examples 'handle uploads' do
end
context "when the file doesn't exist" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
+ end
+
it "responds with status 404" do
show_upload
@@ -102,11 +108,6 @@ shared_examples 'handle uploads' do
end
context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
it "responds with status 200" do
show_upload
@@ -115,6 +116,10 @@ shared_examples 'handle uploads' do
end
context "when the file doesn't exist" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
+ end
+
it "responds with status 404" do
show_upload
@@ -131,11 +136,6 @@ shared_examples 'handle uploads' do
context "when not signed in" do
context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
context "when the file is an image" do
before do
allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
@@ -149,6 +149,10 @@ shared_examples 'handle uploads' do
end
context "when the file is not an image" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:image?).and_return(false)
+ end
+
it "redirects to the sign in page" do
show_upload
@@ -158,6 +162,10 @@ shared_examples 'handle uploads' do
end
context "when the file doesn't exist" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
+ end
+
it "redirects to the sign in page" do
show_upload
@@ -177,11 +185,6 @@ shared_examples 'handle uploads' do
end
context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
it "responds with status 200" do
show_upload
@@ -190,6 +193,10 @@ shared_examples 'handle uploads' do
end
context "when the file doesn't exist" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
+ end
+
it "responds with status 404" do
show_upload
@@ -200,11 +207,6 @@ shared_examples 'handle uploads' do
context "when the user doesn't have access to the model" do
context "when the file exists" do
- before do
- allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg)
- allow(jpg).to receive(:exists?).and_return(true)
- end
-
context "when the file is an image" do
before do
allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
@@ -218,6 +220,10 @@ shared_examples 'handle uploads' do
end
context "when the file is not an image" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:image?).and_return(false)
+ end
+
it "responds with status 404" do
show_upload
@@ -227,6 +233,10 @@ shared_examples 'handle uploads' do
end
context "when the file doesn't exist" do
+ before do
+ allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
+ end
+
it "responds with status 404" do
show_upload
diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
new file mode 100644
index 00000000000..934d53e7bba
--- /dev/null
+++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
@@ -0,0 +1,48 @@
+shared_examples "matches the method pattern" do |method|
+ let(:target) { subject }
+ let(:args) { nil }
+ let(:pattern) { patterns[method] }
+
+ it do
+ return skip "No pattern provided, skipping." unless pattern
+
+ expect(target.method(method).call(*args)).to match(pattern)
+ end
+end
+
+shared_examples "builds correct paths" do |**patterns|
+ let(:patterns) { patterns }
+
+ before do
+ allow(subject).to receive(:filename).and_return('')
+ end
+
+ describe "#store_dir" do
+ it_behaves_like "matches the method pattern", :store_dir
+ end
+
+ describe "#cache_dir" do
+ it_behaves_like "matches the method pattern", :cache_dir
+ end
+
+ describe "#work_dir" do
+ it_behaves_like "matches the method pattern", :work_dir
+ end
+
+ describe "#upload_path" do
+ it_behaves_like "matches the method pattern", :upload_path
+ end
+
+ describe ".absolute_path" do
+ it_behaves_like "matches the method pattern", :absolute_path do
+ let(:target) { subject.class }
+ let(:args) { [upload] }
+ end
+ end
+
+ describe ".base_dir" do
+ it_behaves_like "matches the method pattern", :base_dir do
+ let(:target) { subject.class }
+ end
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 9e5f08fbc51..c275522159c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -237,7 +237,7 @@ module TestEnv
end
def artifacts_path
- Gitlab.config.artifacts.path
+ Gitlab.config.artifacts.storage_path
end
# When no cached assets exist, manually hit the root path to create them
diff --git a/spec/support/track_untracked_uploads_helpers.rb b/spec/support/track_untracked_uploads_helpers.rb
index d05eda08201..5752078d2a0 100644
--- a/spec/support/track_untracked_uploads_helpers.rb
+++ b/spec/support/track_untracked_uploads_helpers.rb
@@ -1,6 +1,6 @@
module TrackUntrackedUploadsHelpers
def uploaded_file
- fixture_path = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')
+ fixture_path = Rails.root.join('spec/fixtures/rails_sample.jpg')
fixture_file_upload(fixture_path)
end
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index 04ee6e9bfad..db378dab97d 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -1,18 +1,16 @@
require 'spec_helper'
describe AttachmentUploader do
- let(:uploader) { described_class.new(build_stubbed(:user)) }
+ let(:note) { create(:note, :with_attachment) }
+ let(:uploader) { note.attachment }
+ let(:upload) { create(:upload, :attachment_upload, model: uploader.model) }
- describe "#store_dir" do
- it "stores in the system dir" do
- expect(uploader.store_dir).to start_with("uploads/-/system/user")
- end
+ subject { uploader }
- it "uses the old path when using object storage" do
- expect(described_class).to receive(:file_storage?).and_return(false)
- expect(uploader.store_dir).to start_with("uploads/user")
- end
- end
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[uploads/-/system/note/attachment/],
+ upload_path: %r[uploads/-/system/note/attachment/],
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/note/attachment/]
describe '#move_to_cache' do
it 'is true' do
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index 1dc574699d8..bf9028c9260 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -1,18 +1,16 @@
require 'spec_helper'
describe AvatarUploader do
- let(:uploader) { described_class.new(build_stubbed(:user)) }
+ let(:model) { create(:user, :with_avatar) }
+ let(:uploader) { described_class.new(model, :avatar) }
+ let(:upload) { create(:upload, model: model) }
- describe "#store_dir" do
- it "stores in the system dir" do
- expect(uploader.store_dir).to start_with("uploads/-/system/user")
- end
+ subject { uploader }
- it "uses the old path when using object storage" do
- expect(described_class).to receive(:file_storage?).and_return(false)
- expect(uploader.store_dir).to start_with("uploads/user")
- end
- end
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[uploads/-/system/user/avatar/],
+ upload_path: %r[uploads/-/system/user/avatar/],
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/user/avatar/]
describe '#move_to_cache' do
it 'is false' do
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index 0cf462e9553..bc024cd307c 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -3,13 +3,13 @@ require 'spec_helper'
describe FileMover do
let(:filename) { 'banana_sample.gif' }
let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) }
+ let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) }
+
let(:temp_description) do
- 'test ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif) same ![banana_sample]'\
- '(/uploads/-/system/temp/secret55/banana_sample.gif)'
+ "test ![banana_sample](/#{temp_file_path}) "\
+ "same ![banana_sample](/#{temp_file_path}) "
end
- let(:temp_file_path) { File.join('secret55', filename).to_s }
- let(:file_path) { File.join('uploads', '-', 'system', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s }
-
+ let(:file_path) { File.join('uploads/-/system/personal_snippet', snippet.id.to_s, 'secret55', filename) }
let(:snippet) { create(:personal_snippet, description: temp_description) }
subject { described_class.new(file_path, snippet).execute }
@@ -28,8 +28,8 @@ describe FileMover do
expect(snippet.reload.description)
.to eq(
- "test ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\
- " same ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"
+ "test ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) "\
+ "same ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) "
)
end
@@ -50,8 +50,8 @@ describe FileMover do
expect(snippet.reload.description)
.to eq(
- "test ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif)"\
- " same ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif)"
+ "test ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif) "\
+ "same ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif) "
)
end
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 845516e5004..a72f853df75 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -1,118 +1,57 @@
require 'spec_helper'
describe FileUploader do
- let(:uploader) { described_class.new(build_stubbed(:project)) }
+ let(:group) { create(:group, name: 'awesome') }
+ let(:project) { create(:project, namespace: group, name: 'project') }
+ let(:uploader) { described_class.new(project) }
+ let(:upload) { double(model: project, path: 'secret/foo.jpg') }
- context 'legacy storage' do
- let(:project) { build_stubbed(:project) }
-
- describe '.absolute_path' do
- it 'returns the correct absolute path by building it dynamically' do
- upload = double(model: project, path: 'secret/foo.jpg')
-
- dynamic_segment = project.full_path
+ subject { uploader }
- expect(described_class.absolute_path(upload))
- .to end_with("#{dynamic_segment}/secret/foo.jpg")
- end
- end
-
- describe "#store_dir" do
- it "stores in the namespace path" do
- uploader = described_class.new(project)
-
- expect(uploader.store_dir).to include(project.full_path)
- expect(uploader.store_dir).not_to include("system")
- end
- end
+ shared_examples 'builds correct legacy storage paths' do
+ include_examples 'builds correct paths',
+ store_dir: %r{awesome/project/\h+},
+ absolute_path: %r{#{described_class.root}/awesome/project/secret/foo.jpg}
end
- context 'hashed storage' do
+ shared_examples 'uses hashed storage' do
context 'when rolled out attachments' do
- let(:project) { build_stubbed(:project, :hashed) }
-
- describe '.absolute_path' do
- it 'returns the correct absolute path by building it dynamically' do
- upload = double(model: project, path: 'secret/foo.jpg')
-
- dynamic_segment = project.disk_path
-
- expect(described_class.absolute_path(upload))
- .to end_with("#{dynamic_segment}/secret/foo.jpg")
- end
+ before do
+ allow(project).to receive(:disk_path).and_return('ca/fe/fe/ed')
end
- describe "#store_dir" do
- it "stores in the namespace path" do
- uploader = described_class.new(project)
+ let(:project) { build_stubbed(:project, :hashed, namespace: group, name: 'project') }
- expect(uploader.store_dir).to include(project.disk_path)
- expect(uploader.store_dir).not_to include("system")
- end
- end
+ it_behaves_like 'builds correct paths',
+ store_dir: %r{ca/fe/fe/ed/\h+},
+ absolute_path: %r{#{described_class.root}/ca/fe/fe/ed/secret/foo.jpg}
end
context 'when only repositories are rolled out' do
- let(:project) { build_stubbed(:project, storage_version: Project::HASHED_STORAGE_FEATURES[:repository]) }
-
- describe '.absolute_path' do
- it 'returns the correct absolute path by building it dynamically' do
- upload = double(model: project, path: 'secret/foo.jpg')
-
- dynamic_segment = project.full_path
-
- expect(described_class.absolute_path(upload))
- .to end_with("#{dynamic_segment}/secret/foo.jpg")
- end
- end
-
- describe "#store_dir" do
- it "stores in the namespace path" do
- uploader = described_class.new(project)
+ let(:project) { build_stubbed(:project, namespace: group, name: 'project', storage_version: Project::HASHED_STORAGE_FEATURES[:repository]) }
- expect(uploader.store_dir).to include(project.full_path)
- expect(uploader.store_dir).not_to include("system")
- end
- end
+ it_behaves_like 'builds correct legacy storage paths'
end
end
- describe 'initialize' do
- it 'generates a secret if none is provided' do
- expect(SecureRandom).to receive(:hex).and_return('secret')
-
- uploader = described_class.new(double)
-
- expect(uploader.secret).to eq 'secret'
- end
-
- it 'accepts a secret parameter' do
- expect(SecureRandom).not_to receive(:hex)
-
- uploader = described_class.new(double, 'secret')
-
- expect(uploader.secret).to eq 'secret'
- end
+ context 'legacy storage' do
+ it_behaves_like 'builds correct legacy storage paths'
+ include_examples 'uses hashed storage'
end
- describe '#move_to_cache' do
- it 'is true' do
- expect(uploader.move_to_cache).to eq(true)
- end
- end
+ describe 'initialize' do
+ let(:uploader) { described_class.new(double, 'secret') }
- describe '#move_to_store' do
- it 'is true' do
- expect(uploader.move_to_store).to eq(true)
+ it 'accepts a secret parameter' do
+ expect(described_class).not_to receive(:generate_secret)
+ expect(uploader.secret).to eq('secret')
end
end
- describe '#relative_path' do
- it 'removes the leading dynamic path segment' do
- fixture = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')
- uploader.store!(fixture_file_upload(fixture))
-
- expect(uploader.relative_path).to match(%r{\A\h{32}/rails_sample.jpg\z})
+ describe '#secret' do
+ it 'generates a secret if none is provided' do
+ expect(described_class).to receive(:generate_secret).and_return('secret')
+ expect(uploader.secret).to eq('secret')
end
end
end
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index a067c3e75f4..d606404e95d 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -3,33 +3,13 @@ require 'spec_helper'
describe JobArtifactUploader do
let(:job_artifact) { create(:ci_job_artifact) }
let(:uploader) { described_class.new(job_artifact, :file) }
- let(:local_path) { Gitlab.config.artifacts.path }
- describe '#store_dir' do
- subject { uploader.store_dir }
+ subject { uploader }
- let(:path) { "#{job_artifact.created_at.utc.strftime('%Y_%m_%d')}/#{job_artifact.job_id}/#{job_artifact.id}" }
-
- context 'when using local storage' do
- it { is_expected.to start_with(local_path) }
- it { is_expected.to match(%r{\h{2}/\h{2}/\h{64}/\d{4}_\d{1,2}_\d{1,2}/\d+/\d+\z}) }
- it { is_expected.to end_with(path) }
- end
- end
-
- describe '#cache_dir' do
- subject { uploader.cache_dir }
-
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('/tmp/cache') }
- end
-
- describe '#work_dir' do
- subject { uploader.work_dir }
-
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('/tmp/work') }
- end
+ it_behaves_like "builds correct paths",
+ store_dir: %r[\h{2}/\h{2}/\h{64}/\d{4}_\d{1,2}_\d{1,2}/\d+/\d+\z],
+ cache_dir: %r[artifacts/tmp/cache],
+ work_dir: %r[artifacts/tmp/work]
context 'file is stored in valid local_path' do
let(:file) do
@@ -43,7 +23,7 @@ describe JobArtifactUploader do
subject { uploader.file.path }
- it { is_expected.to start_with(local_path) }
+ it { is_expected.to start_with("#{uploader.root}/#{uploader.class.base_dir}") }
it { is_expected.to include("/#{job_artifact.created_at.utc.strftime('%Y_%m_%d')}/") }
it { is_expected.to include("/#{job_artifact.job_id}/#{job_artifact.id}/") }
it { is_expected.to end_with("ci_build_artifacts.zip") }
diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb
index efeffb78772..54c6a8b869b 100644
--- a/spec/uploaders/legacy_artifact_uploader_spec.rb
+++ b/spec/uploaders/legacy_artifact_uploader_spec.rb
@@ -3,49 +3,22 @@ require 'rails_helper'
describe LegacyArtifactUploader do
let(:job) { create(:ci_build) }
let(:uploader) { described_class.new(job, :legacy_artifacts_file) }
- let(:local_path) { Gitlab.config.artifacts.path }
+ let(:local_path) { described_class.root }
- describe '.local_store_path' do
- subject { described_class.local_store_path }
+ subject { uploader }
- it "delegate to artifacts path" do
- expect(Gitlab.config.artifacts).to receive(:path)
-
- subject
- end
- end
-
- describe '.artifacts_upload_path' do
- subject { described_class.artifacts_upload_path }
+ # TODO: move to Workhorse::UploadPath
+ describe '.workhorse_upload_path' do
+ subject { described_class.workhorse_upload_path }
it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('tmp/uploads/') }
- end
-
- describe '#store_dir' do
- subject { uploader.store_dir }
-
- let(:path) { "#{job.created_at.utc.strftime('%Y_%m')}/#{job.project_id}/#{job.id}" }
-
- context 'when using local storage' do
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with(path) }
- end
+ it { is_expected.to end_with('tmp/uploads') }
end
- describe '#cache_dir' do
- subject { uploader.cache_dir }
-
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('/tmp/cache') }
- end
-
- describe '#work_dir' do
- subject { uploader.work_dir }
-
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('/tmp/work') }
- end
+ it_behaves_like "builds correct paths",
+ store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z],
+ cache_dir: %r[artifacts/tmp/cache],
+ work_dir: %r[artifacts/tmp/work]
describe '#filename' do
# we need to use uploader, as this makes to use mounter
@@ -69,7 +42,7 @@ describe LegacyArtifactUploader do
subject { uploader.file.path }
- it { is_expected.to start_with(local_path) }
+ it { is_expected.to start_with("#{uploader.root}") }
it { is_expected.to include("/#{job.created_at.utc.strftime('%Y_%m')}/") }
it { is_expected.to include("/#{job.project_id}/") }
it { is_expected.to end_with("ci_build_artifacts.zip") }
diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb
index 7088bc23334..6ebc885daa8 100644
--- a/spec/uploaders/lfs_object_uploader_spec.rb
+++ b/spec/uploaders/lfs_object_uploader_spec.rb
@@ -2,39 +2,13 @@ require 'spec_helper'
describe LfsObjectUploader do
let(:lfs_object) { create(:lfs_object, :with_file) }
- let(:uploader) { described_class.new(lfs_object) }
+ let(:uploader) { described_class.new(lfs_object, :file) }
let(:path) { Gitlab.config.lfs.storage_path }
- describe '#move_to_cache' do
- it 'is true' do
- expect(uploader.move_to_cache).to eq(true)
- end
- end
+ subject { uploader }
- describe '#move_to_store' do
- it 'is true' do
- expect(uploader.move_to_store).to eq(true)
- end
- end
-
- describe '#store_dir' do
- subject { uploader.store_dir }
-
- it { is_expected.to start_with(path) }
- it { is_expected.to end_with("#{lfs_object.oid[0, 2]}/#{lfs_object.oid[2, 2]}") }
- end
-
- describe '#cache_dir' do
- subject { uploader.cache_dir }
-
- it { is_expected.to start_with(path) }
- it { is_expected.to end_with('/tmp/cache') }
- end
-
- describe '#work_dir' do
- subject { uploader.work_dir }
-
- it { is_expected.to start_with(path) }
- it { is_expected.to end_with('/tmp/work') }
- end
+ it_behaves_like "builds correct paths",
+ store_dir: %r[\h{2}/\h{2}],
+ cache_dir: %r[/lfs-objects/tmp/cache],
+ work_dir: %r[/lfs-objects/tmp/work]
end
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
index c6c4500c179..24a2fc0f72e 100644
--- a/spec/uploaders/namespace_file_uploader_spec.rb
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -1,21 +1,16 @@
require 'spec_helper'
+IDENTIFIER = %r{\h+/\S+}
+
describe NamespaceFileUploader do
let(:group) { build_stubbed(:group) }
let(:uploader) { described_class.new(group) }
+ let(:upload) { create(:upload, :namespace_upload, model: group) }
- describe "#store_dir" do
- it "stores in the namespace id directory" do
- expect(uploader.store_dir).to include(group.id.to_s)
- end
- end
-
- describe ".absolute_path" do
- it "stores in thecorrect directory" do
- upload_record = create(:upload, :namespace_upload, model: group)
+ subject { uploader }
- expect(described_class.absolute_path(upload_record))
- .to include("-/system/namespace/#{group.id}")
- end
- end
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[uploads/-/system/namespace/\d+],
+ upload_path: IDENTIFIER,
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/namespace/\d+/#{IDENTIFIER}]
end
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index cbafa9f478d..ed1fba6edda 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -1,25 +1,27 @@
require 'spec_helper'
+IDENTIFIER = %r{\h+/\S+}
+
describe PersonalFileUploader do
- let(:uploader) { described_class.new(build_stubbed(:project)) }
- let(:snippet) { create(:personal_snippet) }
+ let(:model) { create(:personal_snippet) }
+ let(:uploader) { described_class.new(model) }
+ let(:upload) { create(:upload, :personal_snippet_upload) }
- describe '.absolute_path' do
- it 'returns the correct absolute path by building it dynamically' do
- upload = double(model: snippet, path: 'secret/foo.jpg')
+ subject { uploader }
- dynamic_segment = "personal_snippet/#{snippet.id}"
-
- expect(described_class.absolute_path(upload)).to end_with("/-/system/#{dynamic_segment}/secret/foo.jpg")
- end
- end
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[uploads/-/system/personal_snippet/\d+],
+ upload_path: IDENTIFIER,
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet/\d+/#{IDENTIFIER}]
describe '#to_h' do
- it 'returns the hass' do
- uploader = described_class.new(snippet, 'secret')
+ before do
+ subject.instance_variable_set(:@secret, 'secret')
+ end
+ it 'is correct' do
allow(uploader).to receive(:file).and_return(double(extension: 'txt', filename: 'file_name'))
- expected_url = "/uploads/-/system/personal_snippet/#{snippet.id}/secret/file_name"
+ expected_url = "/uploads/-/system/personal_snippet/#{model.id}/secret/file_name"
expect(uploader.to_h).to eq(
alt: 'file_name',
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 7ef7fb7d758..9a3e5d83e01 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -3,16 +3,16 @@ require 'rails_helper'
describe RecordsUploads do
let!(:uploader) do
class RecordsUploadsExampleUploader < GitlabUploader
- include RecordsUploads
+ include RecordsUploads::Concern
storage :file
- def model
- FactoryBot.build_stubbed(:user)
+ def dynamic_segment
+ 'co/fe/ee'
end
end
- RecordsUploadsExampleUploader.new
+ RecordsUploadsExampleUploader.new(build_stubbed(:user))
end
def upload_fixture(filename)
@@ -20,48 +20,55 @@ describe RecordsUploads do
end
describe 'callbacks' do
- it 'calls `record_upload` after `store`' do
+ let(:upload) { create(:upload) }
+
+ before do
+ uploader.upload = upload
+ end
+
+ it '#record_upload after `store`' do
expect(uploader).to receive(:record_upload).once
uploader.store!(upload_fixture('doc_sample.txt'))
end
- it 'calls `destroy_upload` after `remove`' do
- expect(uploader).to receive(:destroy_upload).once
-
+ it '#destroy_upload after `remove`' do
uploader.store!(upload_fixture('doc_sample.txt'))
+ expect(uploader).to receive(:destroy_upload).once
uploader.remove!
end
end
describe '#record_upload callback' do
- it 'returns early when not using file storage' do
- allow(uploader).to receive(:file_storage?).and_return(false)
- expect(Upload).not_to receive(:record)
-
- uploader.store!(upload_fixture('rails_sample.jpg'))
+ it 'creates an Upload record after store' do
+ expect { uploader.store!(upload_fixture('rails_sample.jpg')) }.to change { Upload.count }.by(1)
end
- it "returns early when the file doesn't exist" do
- allow(uploader).to receive(:file).and_return(double(exists?: false))
- expect(Upload).not_to receive(:record)
-
+ it 'creates a new record and assigns size, path, model, and uploader' do
uploader.store!(upload_fixture('rails_sample.jpg'))
+
+ upload = uploader.upload
+ aggregate_failures do
+ expect(upload).to be_persisted
+ expect(upload.size).to eq uploader.file.size
+ expect(upload.path).to eq uploader.upload_path
+ expect(upload.model_id).to eq uploader.model.id
+ expect(upload.model_type).to eq uploader.model.class.to_s
+ expect(upload.uploader).to eq uploader.class.to_s
+ end
end
- it 'creates an Upload record after store' do
- expect(Upload).to receive(:record)
- .with(uploader)
+ it "does not create an Upload record when the file doesn't exist" do
+ allow(uploader).to receive(:file).and_return(double(exists?: false))
- uploader.store!(upload_fixture('rails_sample.jpg'))
+ expect { uploader.store!(upload_fixture('rails_sample.jpg')) }.not_to change { Upload.count }
end
it 'does not create an Upload record if model is missing' do
- expect_any_instance_of(RecordsUploadsExampleUploader).to receive(:model).and_return(nil)
- expect(Upload).not_to receive(:record).with(uploader)
+ allow_any_instance_of(RecordsUploadsExampleUploader).to receive(:model).and_return(nil)
- uploader.store!(upload_fixture('rails_sample.jpg'))
+ expect { uploader.store!(upload_fixture('rails_sample.jpg')) }.not_to change { Upload.count }
end
it 'it destroys Upload records at the same path before recording' do
@@ -72,29 +79,15 @@ describe RecordsUploads do
uploader: uploader.class.to_s
)
+ uploader.upload = existing
uploader.store!(upload_fixture('rails_sample.jpg'))
expect { existing.reload }.to raise_error(ActiveRecord::RecordNotFound)
- expect(Upload.count).to eq 1
+ expect(Upload.count).to eq(1)
end
end
describe '#destroy_upload callback' do
- it 'returns early when not using file storage' do
- uploader.store!(upload_fixture('rails_sample.jpg'))
-
- allow(uploader).to receive(:file_storage?).and_return(false)
- expect(Upload).not_to receive(:remove_path)
-
- uploader.remove!
- end
-
- it 'returns early when file is nil' do
- expect(Upload).not_to receive(:remove_path)
-
- uploader.remove!
- end
-
it 'it destroys Upload records at the same path after removal' do
uploader.store!(upload_fixture('rails_sample.jpg'))
diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb
index 911360da66c..9e50ce15871 100644
--- a/spec/workers/upload_checksum_worker_spec.rb
+++ b/spec/workers/upload_checksum_worker_spec.rb
@@ -2,18 +2,31 @@ require 'rails_helper'
describe UploadChecksumWorker do
describe '#perform' do
- it 'rescues ActiveRecord::RecordNotFound' do
- expect { described_class.new.perform(999_999) }.not_to raise_error
+ subject { described_class.new }
+
+ context 'without a valid record' do
+ it 'rescues ActiveRecord::RecordNotFound' do
+ expect { subject.perform(999_999) }.not_to raise_error
+ end
end
- it 'calls calculate_checksum_without_delay and save!' do
- upload = spy
- expect(Upload).to receive(:find).with(999_999).and_return(upload)
+ context 'with a valid record' do
+ let(:upload) { create(:user, :with_avatar).avatar.upload }
+
+ before do
+ expect(Upload).to receive(:find).and_return(upload)
+ allow(upload).to receive(:foreground_checksumable?).and_return(false)
+ end
- described_class.new.perform(999_999)
+ it 'calls calculate_checksum!' do
+ expect(upload).to receive(:calculate_checksum!)
+ subject.perform(upload.id)
+ end
- expect(upload).to have_received(:calculate_checksum)
- expect(upload).to have_received(:save!)
+ it 'calls save!' do
+ expect(upload).to receive(:save!)
+ subject.perform(upload.id)
+ end
end
end
end
--
cgit v1.2.1
From e3d32e8a2689bba9497d22fa6da1d9946df67f27 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mica=C3=ABl=20Bergeron?=
Date: Tue, 30 Jan 2018 08:15:34 -0500
Subject: fix specs
---
app/models/group.rb | 1 -
app/models/upload.rb | 2 +-
db/migrate/20180119135717_add_uploader_index_to_uploads.rb | 4 ++--
db/schema.rb | 6 ++++--
.../gitlab/background_migration/prepare_untracked_uploads_spec.rb | 4 ----
5 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/app/models/group.rb b/app/models/group.rb
index 415507a6aea..62b1322ebe6 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -37,7 +37,6 @@ class Group < Namespace
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
-
after_create :post_create_hook
after_destroy :post_destroy_hook
after_save :update_two_factor_requirement
diff --git a/app/models/upload.rb b/app/models/upload.rb
index a9f144d2fa2..fb55fd8007b 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -47,7 +47,7 @@ class Upload < ActiveRecord::Base
end
def local?
- return true
+ true
end
def foreground_checksummable?
diff --git a/db/migrate/20180119135717_add_uploader_index_to_uploads.rb b/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
index a678c3d049f..7fedfaa5d7d 100644
--- a/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
+++ b/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
@@ -9,12 +9,12 @@ class AddUploaderIndexToUploads < ActiveRecord::Migration
disable_ddl_transaction!
def up
- remove_concurrent_index :uploads, :path
+ remove_concurrent_index :uploads, :path if index_exists? :uploads, :path
add_concurrent_index :uploads, [:uploader, :path], using: :btree
end
def down
- remove_concurrent_index :uploads, [:uploader, :path]
+ remove_concurrent_index :uploads, [:uploader, :path] if index_exists? :uploads, [:uploads, :path]
add_concurrent_index :uploads, :path, using: :btree
end
end
diff --git a/db/schema.rb b/db/schema.rb
index 4e82a688725..7e632fe2174 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180115201419) do
+ActiveRecord::Schema.define(version: 20180129193323) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1751,11 +1751,13 @@ ActiveRecord::Schema.define(version: 20180115201419) do
t.string "model_type"
t.string "uploader", null: false
t.datetime "created_at", null: false
+ t.string "mount_point"
+ t.string "secret"
end
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
add_index "uploads", ["model_id", "model_type"], name: "index_uploads_on_model_id_and_model_type", using: :btree
- add_index "uploads", ["path"], name: "index_uploads_on_path", using: :btree
+ add_index "uploads", ["uploader", "path"], name: "index_uploads_on_uploader_and_path", using: :btree
create_table "user_agent_details", force: :cascade do |t|
t.string "user_agent", null: false
diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
index 1a4b9f427d6..370c2490b97 100644
--- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
@@ -132,10 +132,6 @@ describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :sidekiq do
context 'when there are files in /uploads/tmp' do
it_behaves_like 'does not add files in /uploads/tmp'
- described_class.new.perform
-
- expect(untracked_files_for_uploads.count).to eq(5)
- end
end
end
end
--
cgit v1.2.1
From e3f42c8d961918ac1d6a2dba712462a5bfe44219 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mica=C3=ABl=20Bergeron?=
Date: Tue, 30 Jan 2018 10:16:39 -0500
Subject: fix the schema.rb
---
db/schema.rb | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/db/schema.rb b/db/schema.rb
index 7e632fe2174..962d25b40b1 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180129193323) do
+ActiveRecord::Schema.define(version: 20180119135717) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1751,8 +1751,6 @@ ActiveRecord::Schema.define(version: 20180129193323) do
t.string "model_type"
t.string "uploader", null: false
t.datetime "created_at", null: false
- t.string "mount_point"
- t.string "secret"
end
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
--
cgit v1.2.1
From 07eb2f1297e3172cc01b3899f0ba799b1e4aafdc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mica=C3=ABl=20Bergeron?=
Date: Tue, 30 Jan 2018 12:38:15 -0500
Subject: fix the migration from not reverting correctly
---
db/migrate/20180119135717_add_uploader_index_to_uploads.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/db/migrate/20180119135717_add_uploader_index_to_uploads.rb b/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
index 7fedfaa5d7d..a678c3d049f 100644
--- a/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
+++ b/db/migrate/20180119135717_add_uploader_index_to_uploads.rb
@@ -9,12 +9,12 @@ class AddUploaderIndexToUploads < ActiveRecord::Migration
disable_ddl_transaction!
def up
- remove_concurrent_index :uploads, :path if index_exists? :uploads, :path
+ remove_concurrent_index :uploads, :path
add_concurrent_index :uploads, [:uploader, :path], using: :btree
end
def down
- remove_concurrent_index :uploads, [:uploader, :path] if index_exists? :uploads, [:uploads, :path]
+ remove_concurrent_index :uploads, [:uploader, :path]
add_concurrent_index :uploads, :path, using: :btree
end
end
--
cgit v1.2.1
From c614089d659398133fcdc1e17a74c9ed7a63a34b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mica=C3=ABl=20Bergeron?=
Date: Thu, 1 Feb 2018 12:13:09 -0500
Subject: porting upstream changes
---
app/uploaders/gitlab_uploader.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index be8ab05b036..3025a0b4ef6 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -35,12 +35,12 @@ class GitlabUploader < CarrierWave::Uploader::Base
# Reduce disk IO
def move_to_cache
- super || true
+ super.nil? ? true : super
end
# Reduce disk IO
def move_to_store
- super || true
+ super.nil? ? true : super
end
def exists?
--
cgit v1.2.1
From 391d1915c8a9e6f723594a6f4930dcd90fe1bc1a Mon Sep 17 00:00:00 2001
From: Mark Fletcher
Date: Thu, 20 Jul 2017 17:54:36 +0700
Subject: Hide pipeline schedule 'take ownership' for current owner
---
app/policies/ci/pipeline_schedule_policy.rb | 8 ++++
.../_pipeline_schedule.html.haml | 3 +-
...-user-interface-bugs-for-schedule-pipelines.yml | 5 +++
spec/policies/ci/pipeline_schedule_policy_spec.rb | 14 +++++++
.../_pipeline_schedule.html.haml_spec.rb | 47 ++++++++++++++++++++++
5 files changed, 76 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml
create mode 100644 spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
diff --git a/app/policies/ci/pipeline_schedule_policy.rb b/app/policies/ci/pipeline_schedule_policy.rb
index abcf536b2f7..dc7a4aed577 100644
--- a/app/policies/ci/pipeline_schedule_policy.rb
+++ b/app/policies/ci/pipeline_schedule_policy.rb
@@ -10,6 +10,10 @@ module Ci
can?(:developer_access) && pipeline_schedule.owned_by?(@user)
end
+ condition(:non_owner_of_schedule) do
+ !pipeline_schedule.owned_by?(@user)
+ end
+
rule { can?(:developer_access) }.policy do
enable :play_pipeline_schedule
end
@@ -19,6 +23,10 @@ module Ci
enable :admin_pipeline_schedule
end
+ rule { can?(:master_access) & non_owner_of_schedule }.policy do
+ enable :take_ownership_pipeline_schedule
+ end
+
rule { protected_ref }.prevent :play_pipeline_schedule
end
end
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 800e234275c..a8692b83b07 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -29,9 +29,10 @@
- if can?(current_user, :play_pipeline_schedule, pipeline_schedule)
= link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('Play'), class: 'btn' do
= icon('play')
- - if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
+ - if can?(current_user, :take_ownership_pipeline_schedule, pipeline_schedule)
= link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do
= s_('PipelineSchedules|Take ownership')
+ - if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
= link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn' do
= icon('pencil')
- if can?(current_user, :admin_pipeline_schedule, pipeline_schedule)
diff --git a/changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml b/changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml
new file mode 100644
index 00000000000..f3a04469884
--- /dev/null
+++ b/changelogs/unreleased/35285-user-interface-bugs-for-schedule-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Hide pipeline schedule take ownership for current owner
+merge_request: 12986
+author:
+type: fixed
diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb
index 1b0e9fac355..c0c3eda4911 100644
--- a/spec/policies/ci/pipeline_schedule_policy_spec.rb
+++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb
@@ -88,5 +88,19 @@ describe Ci::PipelineSchedulePolicy, :models do
expect(policy).to be_allowed :admin_pipeline_schedule
end
end
+
+ describe 'rules for non-owner of schedule' do
+ let(:owner) { create(:user) }
+
+ before do
+ project.add_master(owner)
+ project.add_master(user)
+ pipeline_schedule.update(owner: owner)
+ end
+
+ it 'includes abilities to take ownership' do
+ expect(policy).to be_allowed :take_ownership_pipeline_schedule
+ end
+ end
end
end
diff --git a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
new file mode 100644
index 00000000000..6e7d8db99c4
--- /dev/null
+++ b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe 'projects/pipeline_schedules/_pipeline_schedule' do
+ let(:owner) { create(:user) }
+ let(:master) { create(:user) }
+ let(:project) { create(:project) }
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
+
+ before do
+ assign(:project, project)
+
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:pipeline_schedule).and_return(pipeline_schedule)
+
+ allow(view).to receive(:can?).and_return(true)
+ end
+
+ context 'taking ownership of schedule' do
+ context 'when non-owner is signed in' do
+ let(:user) { master }
+
+ before do
+ allow(view).to receive(:can?).with(master, :take_ownership_pipeline_schedule, pipeline_schedule).and_return(true)
+ end
+
+ it 'non-owner can take ownership of pipeline' do
+ render
+
+ expect(rendered).to have_link('Take ownership')
+ end
+ end
+
+ context 'when owner is signed in' do
+ let(:user) { owner }
+
+ before do
+ allow(view).to receive(:can?).with(owner, :take_ownership_pipeline_schedule, pipeline_schedule).and_return(false)
+ end
+
+ it 'owner cannot take ownership of pipeline' do
+ render
+
+ expect(rendered).not_to have_link('Take ownership')
+ end
+ end
+ end
+end
--
cgit v1.2.1
From ebc32465b6ee007c11cf1c277746cbfe750f837e Mon Sep 17 00:00:00 2001
From: Filipa Lacerda
Date: Thu, 1 Feb 2018 17:37:54 +0000
Subject: Moves more mr widget components into vue files
---
.../components/mr_widget_author.js | 28 --------
.../components/mr_widget_author.vue | 33 +++++++++
.../components/mr_widget_author_time.js | 27 --------
.../components/mr_widget_author_time.vue | 42 ++++++++++++
.../components/states/mr_widget_closed.vue | 2 +-
.../mr_widget_merge_when_pipeline_succeeds.vue | 2 +-
.../components/states/mr_widget_merged.vue | 2 +-
.../components/mr_widget_author_spec.js | 58 ++++++++--------
.../components/mr_widget_author_time_spec.js | 79 ++++++++--------------
9 files changed, 136 insertions(+), 137 deletions(-)
delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js
create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js
create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js
deleted file mode 100644
index 982b5e8e373..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import tooltip from '../../vue_shared/directives/tooltip';
-
-export default {
- name: 'MRWidgetAuthor',
- props: {
- author: { type: Object, required: true },
- showAuthorName: { type: Boolean, required: false, default: true },
- showAuthorTooltip: { type: Boolean, required: false, default: false },
- },
- directives: {
- tooltip,
- },
- template: `
-
-
- {{author.name}}
-
-
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
new file mode 100644
index 00000000000..7ac9eadcde0
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+ {{ author.name }}
+
+
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js
deleted file mode 100644
index 6d2ed5fda64..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import MRWidgetAuthor from './mr_widget_author';
-
-export default {
- name: 'MRWidgetAuthorTime',
- props: {
- actionText: { type: String, required: true },
- author: { type: Object, required: true },
- dateTitle: { type: String, required: true },
- dateReadable: { type: String, required: true },
- },
- components: {
- 'mr-widget-author': MRWidgetAuthor,
- },
- template: `
-
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
new file mode 100644
index 00000000000..8f1fd809a81
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
@@ -0,0 +1,42 @@
+
+
+
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
index 71bfdaf801e..68b691fc914 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
@@ -1,5 +1,5 @@
+
+
+
+
+
+
+
+
diff --git a/changelogs/unreleased/add-confirmation-input-for-modals.yml b/changelogs/unreleased/add-confirmation-input-for-modals.yml
new file mode 100644
index 00000000000..ff1027bc55a
--- /dev/null
+++ b/changelogs/unreleased/add-confirmation-input-for-modals.yml
@@ -0,0 +1,5 @@
+---
+title: Add confirmation-input component
+merge_request: 16816
+author:
+type: other
diff --git a/spec/javascripts/vue_shared/components/confirmation_input_spec.js b/spec/javascripts/vue_shared/components/confirmation_input_spec.js
new file mode 100644
index 00000000000..a6a12614e77
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/confirmation_input_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+import confirmationInput from '~/vue_shared/components/confirmation_input.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Confirmation input component', () => {
+ const Component = Vue.extend(confirmationInput);
+ const props = {
+ inputId: 'dummy-id',
+ confirmationKey: 'confirmation-key',
+ confirmationValue: 'confirmation-value',
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('props', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ it('sets id of the input field to inputId', () => {
+ expect(vm.$refs.enteredValue.id).toBe(props.inputId);
+ });
+
+ it('sets name of the input field to confirmationKey', () => {
+ expect(vm.$refs.enteredValue.name).toBe(props.confirmationKey);
+ });
+ });
+
+ describe('computed', () => {
+ describe('inputLabel', () => {
+ it('escapes confirmationValue by default', () => {
+ vm = mountComponent(Component, { ...props, confirmationValue: 'n ds escap"ng' });
+ expect(vm.inputLabel).toBe('Type n<e></e>ds escap"ng
to confirm:');
+ });
+
+ it('does not escape confirmationValue if escapeValue is false', () => {
+ vm = mountComponent(Component, { ...props, confirmationValue: 'n ds escap"ng', shouldEscapeConfirmationValue: false });
+ expect(vm.inputLabel).toBe('Type n ds escap"ng
to confirm:');
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('hasCorrectValue', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ it('returns false if entered value is incorrect', () => {
+ vm.$refs.enteredValue.value = 'incorrect';
+ expect(vm.hasCorrectValue()).toBe(false);
+ });
+
+ it('returns true if entered value is correct', () => {
+ vm.$refs.enteredValue.value = props.confirmationValue;
+ expect(vm.hasCorrectValue()).toBe(true);
+ });
+ });
+ });
+});
--
cgit v1.2.1
From ece547f4f9523279ab5940dc53499b9b03bfeceb Mon Sep 17 00:00:00 2001
From: Marcia Ramos
Date: Thu, 1 Feb 2018 19:49:07 +0000
Subject: Docs: update GitLab products description
---
doc/README.md | 28 ++++++----------------------
1 file changed, 6 insertions(+), 22 deletions(-)
diff --git a/doc/README.md b/doc/README.md
index 330670587f5..c8b6b4f32b8 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -8,23 +8,13 @@ comments: false
Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured
platform for software development!
-GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans:
+GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans.
-- **GitLab Community Edition (CE)** is an [open source product](https://gitlab.com/gitlab-org/gitlab-ce/),
-self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com.
-- **GitLab Enterprise Edition (EE)** is an [open-core product](https://gitlab.com/gitlab-org/gitlab-ee/),
-self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)**, **GitLab Enterprise Edition Premium (EEP)**, and **GitLab Enterprise Edition Ultimate (EEU)**.
-- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
+With GitLab self-hosted, you deploy your own GitLab instance on-premises or on a private cloud of your choice. GitLab self-hosted is available for [free and with paid subscriptions](https://about.gitlab.com/products/): Libre, Starter, Premium, and Ultimate.
-> **GitLab EE** contains all features available in **GitLab CE**,
-plus premium features available in each version: **Enterprise Edition Starter**
-(**EES**), **Enterprise Edition Premium** (**EEP**), and **Enterprise Edition Ultimate**
-(**EEU**). Everything available in **EES** is also available in **EEP**. Every feature
-available in **EEP** is also available in **EEU**.
+GitLab.com is our SaaS offering. It's hosted, managed, and administered by GitLab, with [free and paid plans](https://about.gitlab.com/gitlab-com/) for individuals and teams: Free, Bronze, Silver, and Gold.
-----
-
-Shortcuts to GitLab's most visited docs:
+## Shortcuts to GitLab's most visited docs
| [GitLab CI/CD](ci/README.md) | Other |
| :----- | :----- |
@@ -134,14 +124,8 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
## Administrator documentation
-[Administration documentation](administration/index.md) applies to admin users of GitLab
-self-hosted instances:
-
-- GitLab Community Edition
-- GitLab [Enterprise Editions](https://about.gitlab.com/gitlab-ee/)
- - Enterprise Edition Starter (EES)
- - Enterprise Edition Premium (EEP)
- - Enterprise Edition Ultimate (EEU)
+[Administration documentation](administration/index.md) applies to admin users of [GitLab
+self-hosted instances](#self-hosted-gitlab): Libre, Starter, Premium, Ultimate.
Learn how to install, configure, update, upgrade, integrate, and maintain your own instance.
Regular users don't have access to GitLab administration tools and settings.
--
cgit v1.2.1
From 658749ddd38f9e0fa6ceb75eddd882e662ba86a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?=
Date: Thu, 1 Feb 2018 17:04:03 -0300
Subject: Fix an issue where sparse checkout wasn't configured correctly
This was affecting the performance of `Gitlab::Git::Repository#squash`.
---
lib/gitlab/git/repository.rb | 2 +-
spec/lib/gitlab/git/repository_spec.rb | 46 +++++++++++++++++++++++++++++++---
2 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 92af5a8e1de..f28624ff37a 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1519,7 +1519,7 @@ module Gitlab
if sparse_checkout_files
# Create worktree without checking out
run_git!(base_args + ['--no-checkout', worktree_path], env: env)
- worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path)
+ worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path).chomp
configure_sparse_checkout(worktree_git_path, sparse_checkout_files)
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 8e0ebb1f6fa..96a442f782f 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -20,6 +20,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:storage_path) { TestEnv.repos_path }
+ let(:user) { build(:user) }
describe '.create_hooks' do
let(:repo_path) { File.join(storage_path, 'hook-test.git') }
@@ -693,7 +694,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#remote_tags' do
let(:remote_name) { 'upstream' }
let(:target_commit_id) { SeedRepo::Commit::ID }
- let(:user) { create(:user) }
let(:tag_name) { 'v0.0.1' }
let(:tag_message) { 'My tag' }
let(:remote_repository) do
@@ -1711,7 +1711,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
shared_examples "user deleting a branch" do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw }
- let(:user) { create(:user) }
let(:branch_name) { "to-be-deleted-soon" }
before do
@@ -1795,7 +1794,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
end
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
- let(:user) { build(:user) }
let(:target_branch) { 'test-merge-target-branch' }
before do
@@ -1848,7 +1846,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
- let(:user) { build(:user) }
let(:target_branch) { 'test-ff-target-branch' }
before do
@@ -2167,6 +2164,47 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
end
end
+
+ describe '#squash' do
+ let(:squash_id) { '1' }
+ let(:branch_name) { 'fix' }
+ let(:start_sha) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+ let(:end_sha) { '12d65c8dd2b2676fa3ac47d955accc085a37a9c1' }
+
+ subject do
+ opts = {
+ branch: branch_name,
+ start_sha: start_sha,
+ end_sha: end_sha,
+ author: user,
+ message: 'Squash commit message'
+ }
+
+ repository.squash(user, squash_id, opts)
+ end
+
+ context 'sparse checkout' do
+ let(:expected_files) { %w(files files/js files/js/application.js) }
+
+ before do
+ allow(repository).to receive(:with_worktree).and_wrap_original do |m, *args|
+ m.call(*args) do
+ worktree_path = args[0]
+ files_pattern = File.join(worktree_path, '**', '*')
+ expected = expected_files.map do |path|
+ File.expand_path(path, worktree_path)
+ end
+
+ expect(Dir[files_pattern]).to eq(expected)
+ end
+ end
+ end
+
+ it 'checkouts only the files in the diff' do
+ subject
+ end
+ end
+ end
end
def create_remote_branch(repository, remote_name, branch_name, source_branch_name)
--
cgit v1.2.1
From a5bb17ffd5d466568c3bcea161bac42123bd3c1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mica=C3=ABl=20Bergeron?=
Date: Thu, 1 Feb 2018 14:24:59 -0500
Subject: porting changes from upstream
---
app/uploaders/gitlab_uploader.rb | 4 ++--
spec/factories/groups.rb | 2 +-
spec/factories/projects.rb | 2 +-
spec/factories/users.rb | 2 +-
spec/uploaders/attachment_uploader_spec.rb | 12 ------------
5 files changed, 5 insertions(+), 17 deletions(-)
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 3025a0b4ef6..b12829efe73 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -35,12 +35,12 @@ class GitlabUploader < CarrierWave::Uploader::Base
# Reduce disk IO
def move_to_cache
- super.nil? ? true : super
+ file_storage?
end
# Reduce disk IO
def move_to_store
- super.nil? ? true : super
+ file_storage?
end
def exists?
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 1512f5a0e58..8c531cf5909 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -18,7 +18,7 @@ FactoryBot.define do
end
trait :with_avatar do
- avatar { File.open(Rails.root.join('spec/fixtures/dk.png')) }
+ avatar { fixture_file_upload('spec/fixtures/dk.png') }
end
trait :access_requestable do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index d0f3911f730..16d328a5bc2 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -90,7 +90,7 @@ FactoryBot.define do
end
trait :with_avatar do
- avatar { File.open(Rails.root.join('spec/fixtures/dk.png')) }
+ avatar { fixture_file_upload('spec/fixtures/dk.png') }
end
trait :broken_storage do
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index e62e0b263ca..769fd656e7a 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -38,7 +38,7 @@ FactoryBot.define do
end
trait :with_avatar do
- avatar { File.open(Rails.root.join('spec/fixtures/dk.png')) }
+ avatar { fixture_file_upload('spec/fixtures/dk.png') }
end
trait :two_factor_via_otp do
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index db378dab97d..091ba824fc6 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -11,16 +11,4 @@ describe AttachmentUploader do
store_dir: %r[uploads/-/system/note/attachment/],
upload_path: %r[uploads/-/system/note/attachment/],
absolute_path: %r[#{CarrierWave.root}/uploads/-/system/note/attachment/]
-
- describe '#move_to_cache' do
- it 'is true' do
- expect(uploader.move_to_cache).to eq(true)
- end
- end
-
- describe '#move_to_store' do
- it 'is true' do
- expect(uploader.move_to_store).to eq(true)
- end
- end
end
--
cgit v1.2.1
From 900124810cd8d456644b9722d5d703abc751f217 Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Mon, 15 Jan 2018 11:50:26 -0600
Subject: generate webpack entry points for pages directory automatically
---
config/webpack.config.js | 12 ++++++++++++
package.json | 1 +
yarn.lock | 2 +-
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 783677b5b8d..ed064be690c 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -3,6 +3,7 @@
var crypto = require('crypto');
var fs = require('fs');
var path = require('path');
+var glob = require('glob');
var webpack = require('webpack');
var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
var CopyWebpackPlugin = require('copy-webpack-plugin');
@@ -20,6 +21,15 @@ var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var NO_COMPRESSION = process.env.NO_COMPRESSION;
+// generate automatic entry points
+var autoEntries = {};
+var pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') });
+
+pageEntries.forEach(( path ) => {
+ let chunkName = path.replace(/\/index\.js$/, '').replace(/\//g, '.');
+ autoEntries[chunkName] = './' + path;
+});
+
var config = {
// because sqljs requires fs.
node: {
@@ -301,6 +311,8 @@ var config = {
}
}
+config.entry = Object.assign({}, autoEntries, config.entry);
+
if (IS_PRODUCTION) {
config.devtool = 'source-map';
config.plugins.push(
diff --git a/package.json b/package.json
index c68cf648932..df0d2bb3676 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"exports-loader": "^0.6.4",
"file-loader": "^0.11.1",
"fuzzaldrin-plus": "^0.5.0",
+ "glob": "^7.1.2",
"imports-loader": "^0.7.1",
"jed": "^1.1.1",
"jquery": "^2.2.4",
diff --git a/yarn.lock b/yarn.lock
index d10a4372a40..e6d5f239d83 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3322,7 +3322,7 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.1.0, glob@~7.1.2:
+glob@^7.1.0, glob@^7.1.2, glob@~7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies:
--
cgit v1.2.1
From 178816068c2dc1ca62487e0b4caa075d5a709d08 Mon Sep 17 00:00:00 2001
From: Oswaldo Ferreira
Date: Thu, 1 Feb 2018 19:17:30 -0200
Subject: Reload MRs memoization after diffs creation
---
app/services/merge_requests/refresh_service.rb | 7 ++++++-
changelogs/unreleased/osw-system-notes-for-commits-regression.yml | 5 +++++
spec/services/merge_requests/refresh_service_spec.rb | 8 ++++++++
3 files changed, 19 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/osw-system-notes-for-commits-regression.yml
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 262622f8bd0..18c40ce8992 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -90,6 +90,10 @@ module MergeRequests
merge_request.mark_as_unchecked
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
+
+ # Upcoming method calls need the refreshed version of
+ # @source_merge_requests diffs (for MergeRequest#commit_shas for instance).
+ merge_requests_for_source_branch(reload: true)
end
def reset_merge_when_pipeline_succeeds
@@ -195,7 +199,8 @@ module MergeRequests
merge_requests.uniq.select(&:source_project)
end
- def merge_requests_for_source_branch
+ def merge_requests_for_source_branch(reload: false)
+ @source_merge_requests = nil if reload
@source_merge_requests ||= merge_requests_for(@branch_name)
end
diff --git a/changelogs/unreleased/osw-system-notes-for-commits-regression.yml b/changelogs/unreleased/osw-system-notes-for-commits-regression.yml
new file mode 100644
index 00000000000..6d0943c7716
--- /dev/null
+++ b/changelogs/unreleased/osw-system-notes-for-commits-regression.yml
@@ -0,0 +1,5 @@
+---
+title: Reload MRs memoization after diffs creation
+merge_request:
+author:
+type: fixed
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 7c3374c6113..89b0e4b7551 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -74,6 +74,14 @@ describe MergeRequests::RefreshService do
expect(@fork_build_failed_todo).to be_done
end
+ it 'reloads source branch MRs memoization' do
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+
+ expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }.to change {
+ refresh_service.instance_variable_get("@source_merge_requests").first.merge_request_diff
+ }
+ end
+
context 'when source branch ref does not exists' do
before do
DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch)
--
cgit v1.2.1
From 972f564dcd897ee66799c19f96ed22df588f8a18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?=
Date: Thu, 1 Feb 2018 17:00:16 -0500
Subject: Sanitize extra blank spaces used when uploading a SSH key
---
app/models/key.rb | 7 ++-
...-blank-spaces-used-when-uploading-a-ssh-key.yml | 5 +++
lib/gitlab/ssh_public_key.rb | 28 +++++++++---
spec/factories/keys.rb | 4 ++
spec/lib/gitlab/ssh_public_key_spec.rb | 35 +++++++++++++++
spec/models/key_spec.rb | 51 +++++++++++++++++++---
6 files changed, 113 insertions(+), 17 deletions(-)
create mode 100644 changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml
diff --git a/app/models/key.rb b/app/models/key.rb
index a3f8a5d6dc7..cfa009f212f 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -34,9 +34,8 @@ class Key < ActiveRecord::Base
after_destroy :refresh_user_cache
def key=(value)
- value&.delete!("\n\r")
- value.strip! unless value.blank?
- write_attribute(:key, value)
+ write_attribute(:key, value.present? ? Gitlab::SSHPublicKey.sanitize(value) : nil)
+
@public_key = nil
end
@@ -98,7 +97,7 @@ class Key < ActiveRecord::Base
def generate_fingerprint
self.fingerprint = nil
- return unless self.key.present?
+ return unless public_key.valid?
self.fingerprint = public_key.fingerprint
end
diff --git a/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml b/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml
new file mode 100644
index 00000000000..9e4811ca308
--- /dev/null
+++ b/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml
@@ -0,0 +1,5 @@
+---
+title: Sanitize extra blank spaces used when uploading a SSH key
+merge_request: 40552
+author:
+type: fixed
diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb
index 89ca1298120..545e7c74f7e 100644
--- a/lib/gitlab/ssh_public_key.rb
+++ b/lib/gitlab/ssh_public_key.rb
@@ -21,6 +21,22 @@ module Gitlab
technology(name)&.supported_sizes
end
+ def self.sanitize(key_content)
+ ssh_type, *parts = key_content.strip.split
+
+ return key_content if parts.empty?
+
+ parts.each_with_object("#{ssh_type} ").with_index do |(part, content), index|
+ content << part
+
+ if Gitlab::SSHPublicKey.new(content).valid?
+ break [content, parts[index + 1]].compact.join(' ') # Add the comment part if present
+ elsif parts.size == index + 1 # return original content if we've reached the last element
+ break key_content
+ end
+ end
+ end
+
attr_reader :key_text, :key
# Unqualified MD5 fingerprint for compatibility
@@ -37,23 +53,23 @@ module Gitlab
end
def valid?
- key.present?
+ key.present? && bits && technology.supported_sizes.include?(bits)
end
def type
- technology.name if valid?
+ technology.name if key.present?
end
def bits
- return unless valid?
+ return if key.blank?
case type
when :rsa
- key.n.num_bits
+ key.n&.num_bits
when :dsa
- key.p.num_bits
+ key.p&.num_bits
when :ecdsa
- key.group.order.num_bits
+ key.group.order&.num_bits
when :ed25519
256
else
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index f0c43f3d6f5..23a98a899f1 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -5,6 +5,10 @@ FactoryBot.define do
title
key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com' }
+ factory :key_without_comment do
+ key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate }
+ end
+
factory :deploy_key, class: 'DeployKey'
factory :personal_key do
diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb
index 93d538141ce..c15e29774b6 100644
--- a/spec/lib/gitlab/ssh_public_key_spec.rb
+++ b/spec/lib/gitlab/ssh_public_key_spec.rb
@@ -37,6 +37,41 @@ describe Gitlab::SSHPublicKey, lib: true do
end
end
+ describe '.sanitize(key_content)' do
+ let(:content) { build(:key).key }
+
+ context 'when key has blank space characters' do
+ it 'removes the extra blank space characters' do
+ unsanitized = content.insert(100, "\n")
+ .insert(40, "\r\n")
+ .insert(30, ' ')
+
+ sanitized = described_class.sanitize(unsanitized)
+ _, body = sanitized.split
+
+ expect(sanitized).not_to eq(unsanitized)
+ expect(body).not_to match(/\s/)
+ end
+ end
+
+ context "when key doesn't have blank space characters" do
+ it "doesn't modify the content" do
+ sanitized = described_class.sanitize(content)
+
+ expect(sanitized).to eq(content)
+ end
+ end
+
+ context "when key is invalid" do
+ it 'returns the original content' do
+ unsanitized = "ssh-foo any content=="
+ sanitized = described_class.sanitize(unsanitized)
+
+ expect(sanitized).to eq(unsanitized)
+ end
+ end
+ end
+
describe '#valid?' do
subject { public_key }
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 4cd9e3f4f1d..f620e13e16d 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -79,16 +79,53 @@ describe Key, :mailer do
expect(build(:key)).to be_valid
end
- it 'accepts a key with newline charecters after stripping them' do
- key = build(:key)
- key.key = key.key.insert(100, "\n")
- key.key = key.key.insert(40, "\r\n")
- expect(key).to be_valid
- end
-
it 'rejects the unfingerprintable key (not a key)' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end
+
+ where(:factory, :chars, :expected_sections) do
+ [
+ [:key, ["\n", "\r\n"], 3],
+ [:key, [' ', ' '], 3],
+ [:key_without_comment, [' ', ' '], 2]
+ ]
+ end
+
+ with_them do
+ let!(:key) { create(factory) }
+ let!(:original_fingerprint) { key.fingerprint }
+
+ it 'accepts a key with blank space characters after stripping them' do
+ modified_key = key.key.insert(100, chars.first).insert(40, chars.last)
+ _, content = modified_key.split
+
+ key.update!(key: modified_key)
+
+ expect(key).to be_valid
+ expect(key.key.split.size).to eq(expected_sections)
+
+ expect(content).not_to match(/\s/)
+ expect(original_fingerprint).to eq(key.fingerprint)
+ end
+ end
+ end
+
+ context 'validate size' do
+ where(:key_content, :result) do
+ [
+ [Spec::Support::Helpers::KeyGeneratorHelper.new(512).generate, false],
+ [Spec::Support::Helpers::KeyGeneratorHelper.new(8192).generate, false],
+ [Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate, true]
+ ]
+ end
+
+ with_them do
+ it 'validates the size of the key' do
+ key = build(:key, key: key_content)
+
+ expect(key.valid?).to eq(result)
+ end
+ end
end
context 'validate it meets key restrictions' do
--
cgit v1.2.1
From 0acbc1b734201a755c9b566f29bca96a967b1ec4 Mon Sep 17 00:00:00 2001
From: Clement Ho
Date: Thu, 1 Feb 2018 17:52:38 -0600
Subject: Replace $.ajax in project.js
---
app/assets/javascripts/pages/projects/project.js | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index e30d558726b..863dac0d20e 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,7 +1,10 @@
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
import Cookies from 'js-cookie';
-import { visitUrl } from '../../lib/utils/url_utility';
+import { __ } from '~/locale';
+import { visitUrl } from '~/lib/utils/url_utility';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
import projectSelect from '../../project_select';
export default class Project {
@@ -67,17 +70,15 @@ export default class Project {
$dropdown = $(this);
selected = $dropdown.data('selected');
return $dropdown.glDropdown({
- data: function(term, callback) {
- return $.ajax({
- url: $dropdown.data('refs-url'),
- data: {
+ data(term, callback) {
+ axios.get($dropdown.data('refs-url'), {
+ params: {
ref: $dropdown.data('ref'),
search: term,
},
- dataType: 'json',
- }).done(function(refs) {
- return callback(refs);
- });
+ })
+ .then(({ data }) => callback(data))
+ .catch(() => flash(__('An error occurred while getting projects')));
},
selectable: true,
filterable: true,
--
cgit v1.2.1
From dd75c337e5ee036fca8188d5b7adbf6e7725570f Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 16:01:36 -0600
Subject: update webpack helper to automatically include route-based entry
points
---
app/helpers/webpack_helper.rb | 18 ++++++++++++++++++
app/views/layouts/_head.html.haml | 1 +
2 files changed, 19 insertions(+)
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 77433acb92a..9d071f2d59a 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -5,6 +5,24 @@ module WebpackHelper
javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: force_same_domain))
end
+ def webpack_controller_bundle_tags
+ bundles = []
+ segments = [*controller.controller_path.split('/'), controller.action_name].compact
+
+ until segments.empty?
+ begin
+ asset_paths = gitlab_webpack_asset_paths("pages.#{segments.join('.')}", extension: 'js')
+ bundles.unshift(*asset_paths)
+ rescue Webpack::Rails::Manifest::EntryPointMissingError
+ # no bundle exists for this path
+ end
+
+ segments.pop
+ end
+
+ javascript_include_tag(*bundles)
+ end
+
# override webpack-rails gem helper until changes can make it upstream
def gitlab_webpack_asset_paths(source, extension: nil, force_same_domain: false)
return "" unless source.present?
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index ea13a5e6d62..1403c8329c8 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -41,6 +41,7 @@
= webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "main"
+ = webpack_controller_bundle_tags
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
--
cgit v1.2.1
From 8ebe2f7ae6872ff8553fb0f3482df209270c874b Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:23:03 -0600
Subject: prevent dynamic chunks from being duplicated by the dispatcher until
it is completely refactored
---
app/assets/javascripts/dispatcher.js | 6 +++---
config/webpack.config.js | 15 +++++++++++++--
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 262ed3783fb..5cd66f7f544 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -56,7 +56,7 @@ import SearchAutocomplete from './search_autocomplete';
break;
case 'projects:boards:show':
case 'projects:boards:index':
- import('./pages/projects/boards/index')
+ import('./pages/projects/boards')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
@@ -363,7 +363,7 @@ import SearchAutocomplete from './search_autocomplete';
.catch(fail);
break;
case 'projects:project_members:index':
- import('./pages/projects/project_members/')
+ import('./pages/projects/project_members')
.then(callDefault)
.catch(fail);
break;
@@ -605,7 +605,7 @@ import SearchAutocomplete from './search_autocomplete';
}
break;
case 'profiles':
- import('./pages/profiles/index/')
+ import('./pages/profiles/index')
.then(callDefault)
.catch(fail);
break;
diff --git a/config/webpack.config.js b/config/webpack.config.js
index ed064be690c..7f3fe551a03 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -25,11 +25,22 @@ var NO_COMPRESSION = process.env.NO_COMPRESSION;
var autoEntries = {};
var pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') });
+// filter out entries currently imported dynamically in dispatcher.js
+var dispatcher = fs.readFileSync(path.join(ROOT_PATH, 'app/assets/javascripts/dispatcher.js')).toString();
+var dispatcherChunks = dispatcher.match(/(?!import\('.\/)pages\/[^']+/g);
+
pageEntries.forEach(( path ) => {
- let chunkName = path.replace(/\/index\.js$/, '').replace(/\//g, '.');
- autoEntries[chunkName] = './' + path;
+ let chunkPath = path.replace(/\/index\.js$/, '');
+ if (!dispatcherChunks.includes(chunkPath)) {
+ let chunkName = chunkPath.replace(/\//g, '.');
+ autoEntries[chunkName] = './' + path;
+ }
});
+// report our auto-generated bundle count
+var autoEntriesCount = Object.keys(autoEntries).length;
+console.log(`${autoEntriesCount} entries from '/pages' automatically added to webpack output.`);
+
var config = {
// because sqljs requires fs.
node: {
--
cgit v1.2.1
From 64f3f5e43b6021180f825135c760ed2c53ac8e6e Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:27:26 -0600
Subject: migrate sessions:new to static bundle
---
app/assets/javascripts/dispatcher.js | 5 -----
app/assets/javascripts/pages/sessions/new/index.js | 4 ++--
2 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 5cd66f7f544..b3549f8730b 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -49,11 +49,6 @@ import SearchAutocomplete from './search_autocomplete';
});
switch (page) {
- case 'sessions:new':
- import('./pages/sessions/new')
- .then(callDefault)
- .catch(fail);
- break;
case 'projects:boards:show':
case 'projects:boards:index':
import('./pages/projects/boards')
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index f163557babc..a0aa0499776 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -2,10 +2,10 @@ import UsernameValidator from './username_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import OAuthRememberMe from './oauth_remember_me';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
new OAuthRememberMe({ // eslint-disable-line no-new
container: $('.omniauth-container'),
}).bindEvents();
-};
+});
--
cgit v1.2.1
From 7b5e0d52d6d43899e8ce937c5a1b5978fda69aab Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:28:05 -0600
Subject: migrate projects:boards:* to static bundle
---
app/assets/javascripts/dispatcher.js | 7 -------
app/assets/javascripts/pages/projects/boards/index.js | 4 ++--
2 files changed, 2 insertions(+), 9 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index b3549f8730b..280ee0d3481 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -49,13 +49,6 @@ import SearchAutocomplete from './search_autocomplete';
});
switch (page) {
- case 'projects:boards:show':
- case 'projects:boards:index':
- import('./pages/projects/boards')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:environments:metrics':
import('./pages/projects/environments/metrics')
.then(callDefault)
diff --git a/app/assets/javascripts/pages/projects/boards/index.js b/app/assets/javascripts/pages/projects/boards/index.js
index 42c9bb5ec99..3aeeedbb45d 100644
--- a/app/assets/javascripts/pages/projects/boards/index.js
+++ b/app/assets/javascripts/pages/projects/boards/index.js
@@ -1,7 +1,7 @@
import UsersSelect from '~/users_select';
import ShortcutsNavigation from '~/shortcuts_navigation';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
-};
+});
--
cgit v1.2.1
From bfdc81e9d5152e1a93064348e48460c31e4ffb8f Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:38:08 -0600
Subject: migrate projects:show to static bundle
---
app/assets/javascripts/dispatcher.js | 3 ---
app/assets/javascripts/pages/projects/show/index.js | 4 ++--
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 280ee0d3481..320f3aa0b53 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -306,9 +306,6 @@ import SearchAutocomplete from './search_autocomplete';
shortcut_handler = true;
break;
case 'projects:show':
- import('./pages/projects/show')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'projects:edit':
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 55154cdddcb..9b87f249f09 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -8,7 +8,7 @@ import { ajaxGet } from '~/lib/utils/common_utils';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
@@ -24,4 +24,4 @@ export default () => {
$('#tree-slider').waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
-};
+});
--
cgit v1.2.1
From 3399338512f0352dcbda31079f48a3f928b01524 Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:51:52 -0600
Subject: migrate projects:merge_requests:index to static bundle
---
app/assets/javascripts/dispatcher.js | 3 ---
app/assets/javascripts/pages/projects/merge_requests/index/index.js | 4 ++--
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 320f3aa0b53..a47de0900a6 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -55,9 +55,6 @@ import SearchAutocomplete from './search_autocomplete';
.catch(fail);
break;
case 'projects:merge_requests:index':
- import('./pages/projects/merge_requests/index')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'projects:issues:index':
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index b386e8fb48d..adadbf28e49 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -5,9 +5,9 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
-};
+});
--
cgit v1.2.1
From 2d45b4e37df07daf9411891777a08286d7c4413e Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:54:23 -0600
Subject: migrate projects:issues:index to static bundle
---
app/assets/javascripts/dispatcher.js | 5 -----
app/assets/javascripts/pages/projects/issues/index/index.js | 4 ++--
2 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index a47de0900a6..aaf41222306 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -55,12 +55,7 @@ import SearchAutocomplete from './search_autocomplete';
.catch(fail);
break;
case 'projects:merge_requests:index':
- shortcut_handler = true;
- break;
case 'projects:issues:index':
- import('./pages/projects/issues/index')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'projects:issues:show':
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index 0d3f35f044d..39c043edc38 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -7,10 +7,10 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.ISSUES);
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
new ShortcutsNavigation();
new UsersSelect();
-};
+});
--
cgit v1.2.1
From cd4a87ee5549ee1ed4878c25d216daaf202240ac Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:55:18 -0600
Subject: migrate projects:issues:show to static bundle
---
app/assets/javascripts/dispatcher.js | 5 -----
app/assets/javascripts/pages/projects/issues/show/index.js | 6 +++---
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index aaf41222306..cbe32fb81de 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -56,12 +56,7 @@ import SearchAutocomplete from './search_autocomplete';
break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
- shortcut_handler = true;
- break;
case 'projects:issues:show':
- import('./pages/projects/issues/show')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'dashboard:milestones:index':
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
index 48ed8fb2243..da312c1f1b7 100644
--- a/app/assets/javascripts/pages/projects/issues/show/index.js
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -1,13 +1,13 @@
-
/* eslint-disable no-new */
+
import initIssuableSidebar from '~/init_issuable_sidebar';
import Issue from '~/issue';
import ShortcutsIssuable from '~/shortcuts_issuable';
import ZenMode from '~/zen_mode';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Issue();
new ShortcutsIssuable();
new ZenMode();
initIssuableSidebar();
-};
+});
--
cgit v1.2.1
From 053c3d0e1ba4a6bb8cdf099b4a0a3eeef919cd62 Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 17:56:38 -0600
Subject: migrate dashboard:milestones:index to static bundle
---
app/assets/javascripts/dispatcher.js | 5 -----
app/assets/javascripts/pages/dashboard/milestones/index/index.js | 2 +-
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index cbe32fb81de..ba54ff8ccb5 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -59,11 +59,6 @@ import SearchAutocomplete from './search_autocomplete';
case 'projects:issues:show':
shortcut_handler = true;
break;
- case 'dashboard:milestones:index':
- import('./pages/dashboard/milestones/index')
- .then(callDefault)
- .catch(fail);
- break;
case 'projects:milestones:index':
import('./pages/projects/milestones/index')
.then(callDefault)
diff --git a/app/assets/javascripts/pages/dashboard/milestones/index/index.js b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
index 0f2f1bd4a25..38ddebe30d9 100644
--- a/app/assets/javascripts/pages/dashboard/milestones/index/index.js
+++ b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
@@ -1,3 +1,3 @@
import projectSelect from '~/project_select';
-export default projectSelect;
+document.addEventListener('DOMContentLoaded', projectSelect);
--
cgit v1.2.1
From f0fe736e322377ee15859bbb2a21fb0de4c5d2fd Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 21:28:50 -0600
Subject: ensure dispatcher is run before other DOMContentLoaded handlers
---
app/assets/javascripts/dispatcher.js | 12 ++++++------
app/assets/javascripts/main.js | 4 +++-
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index ba54ff8ccb5..242c1c06a33 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -12,9 +12,9 @@ import ShortcutsIssuable from './shortcuts_issuable';
import Diff from './diff';
import SearchAutocomplete from './search_autocomplete';
-(function() {
- var Dispatcher;
+var Dispatcher;
+(function() {
Dispatcher = (function() {
function Dispatcher() {
this.initSearch();
@@ -629,8 +629,8 @@ import SearchAutocomplete from './search_autocomplete';
return Dispatcher;
})();
+})();
- $(window).on('load', function() {
- new Dispatcher();
- });
-}).call(window);
+export default function initDispatcher() {
+ return new Dispatcher();
+}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index d8b881a8fac..39445a85c77 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -33,7 +33,7 @@ import './projects_dropdown';
import './render_gfm';
import initBreadcrumbs from './breadcrumb';
-import './dispatcher';
+import initDispatcher from './dispatcher';
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
@@ -265,4 +265,6 @@ $(() => {
removeFlashClickListener(flashEl);
});
}
+
+ initDispatcher();
});
--
cgit v1.2.1
From ac85d98cc3c08c56ae02158effa1e59739ba3485 Mon Sep 17 00:00:00 2001
From: Mike Greiling
Date: Thu, 1 Feb 2018 21:34:01 -0600
Subject: move auto-generated bundle script tags after manual page-specific
webpack bundles
---
app/views/layouts/_head.html.haml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 1403c8329c8..3fbdb4b9463 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -41,13 +41,14 @@
= webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "main"
- = webpack_controller_bundle_tags
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
+ = webpack_controller_bundle_tags
+
= yield :project_javascripts
= csrf_meta_tags
--
cgit v1.2.1
From f8490a4f35fbb2c787c993280bfdce75614da216 Mon Sep 17 00:00:00 2001
From: Toon Claes
Date: Fri, 2 Feb 2018 07:04:02 +0000
Subject: Place brand_image outside tag
---
app/views/layouts/devise.html.haml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 4e9ea33e675..a95c834dcfd 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -15,7 +15,7 @@
.col-sm-7.brand-holder.pull-left
%h1
= brand_title
- = brand_image
+ = brand_image
- if brand_item&.description?
= brand_text
- else
--
cgit v1.2.1
From 645d635976ef48905c0416bd2a4a25025fef9ee4 Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 2 Feb 2018 10:05:37 +0000
Subject: fixed issue with axios_utils not reducing activeVueResources variable
when request fails
---
app/assets/javascripts/job.js | 13 ++++++++++++-
app/assets/javascripts/lib/utils/axios_utils.js | 4 ++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index d2cbc9b82b3..d0b7ea75082 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -9,6 +9,7 @@ export default class Job {
constructor(options) {
this.timeout = null;
this.state = null;
+ this.fetchingStatusFavicon = false;
this.options = options || $('.js-build-options').data();
this.pagePath = this.options.pagePath;
@@ -178,7 +179,17 @@ export default class Job {
.then((res) => {
const log = res.data;
- setCiStatusFavicon(`${this.pagePath}/status.json`);
+ if (!this.fetchingStatusFavicon) {
+ this.fetchingStatusFavicon = true;
+
+ setCiStatusFavicon(`${this.pagePath}/status.json`)
+ .then(() => {
+ this.fetchingStatusFavicon = false;
+ })
+ .catch(() => {
+ this.fetchingStatusFavicon = false;
+ });
+ }
if (log.state) {
this.state = log.state;
diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
index 585214049c7..792871e2ecf 100644
--- a/app/assets/javascripts/lib/utils/axios_utils.js
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -19,6 +19,10 @@ axios.interceptors.response.use((config) => {
window.activeVueResources -= 1;
return config;
+}, (e) => {
+ window.activeVueResources -= 1;
+
+ return Promise.reject(e);
});
export default axios;
--
cgit v1.2.1
From 948150f050d14811f6fe1b327387e3c2c1b1fe31 Mon Sep 17 00:00:00 2001
From: Bob Van Landuyt
Date: Fri, 2 Feb 2018 11:23:05 +0100
Subject: Avoid error when no restricted levels are defined
When no visibility levels are defined they could sometimes return
`nil` instead of an empty array. In this case we want to allow all
levels.
---
.../bvl-fix-500-on-fork-without-restricted-visibility-levels.yml | 5 +++++
lib/gitlab/visibility_level.rb | 2 +-
spec/lib/gitlab/visibility_level_spec.rb | 9 +++++++++
3 files changed, 15 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml
diff --git a/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml b/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml
new file mode 100644
index 00000000000..378f0ef7ce9
--- /dev/null
+++ b/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml
@@ -0,0 +1,5 @@
+---
+title: Fix forking projects when no restricted visibility levels are defined applicationwide
+merge_request: 16881
+author:
+type: fixed
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 6ced06a863d..0b757b2a646 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -60,7 +60,7 @@ module Gitlab
def allowed_levels
restricted_levels = current_application_settings.restricted_visibility_levels
- self.values - restricted_levels
+ self.values - Array(restricted_levels)
end
def closest_allowed_level(target_level)
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index d85dac630b4..2c1146ceff5 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -57,6 +57,15 @@ describe Gitlab::VisibilityLevel do
expect(described_class.allowed_levels)
.to contain_exactly(described_class::PRIVATE, described_class::PUBLIC)
end
+
+ it 'returns all levels when no visibility level was set' do
+ allow(described_class)
+ .to receive_message_chain('current_application_settings.restricted_visibility_levels')
+ .and_return(nil)
+
+ expect(described_class.allowed_levels)
+ .to contain_exactly(described_class::PRIVATE, described_class::INTERNAL, described_class::PUBLIC)
+ end
end
describe '.closest_allowed_level' do
--
cgit v1.2.1
From 313f4d49d7aea0318bbabeae529d58076519aea0 Mon Sep 17 00:00:00 2001
From: Andreas Brandl
Date: Thu, 1 Feb 2018 11:32:22 +0100
Subject: Add unique constraint to trending_projects#project_id.
---
.../32283-trending-projects-unique-constraint2.yml | 5 +++++
...ique_constraint_to_trending_projects_project_id.rb | 19 +++++++++++++++++++
db/schema.rb | 2 +-
3 files changed, 25 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/32283-trending-projects-unique-constraint2.yml
create mode 100644 db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb
diff --git a/changelogs/unreleased/32283-trending-projects-unique-constraint2.yml b/changelogs/unreleased/32283-trending-projects-unique-constraint2.yml
new file mode 100644
index 00000000000..4fd6b6fddc4
--- /dev/null
+++ b/changelogs/unreleased/32283-trending-projects-unique-constraint2.yml
@@ -0,0 +1,5 @@
+---
+title: Add unique constraint to trending_projects#project_id.
+merge_request: 16846
+author:
+type: other
diff --git a/db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb b/db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb
new file mode 100644
index 00000000000..02e53b8fa8a
--- /dev/null
+++ b/db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb
@@ -0,0 +1,19 @@
+class AddUniqueConstraintToTrendingProjectsProjectId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :trending_projects, :project_id, unique: true, name: 'index_trending_projects_on_project_id_unique'
+ remove_concurrent_index_by_name :trending_projects, 'index_trending_projects_on_project_id'
+ rename_index :trending_projects, 'index_trending_projects_on_project_id_unique', 'index_trending_projects_on_project_id'
+ end
+
+ def down
+ rename_index :trending_projects, 'index_trending_projects_on_project_id', 'index_trending_projects_on_project_id_old'
+ add_concurrent_index :trending_projects, :project_id
+ remove_concurrent_index_by_name :trending_projects, 'index_trending_projects_on_project_id_old'
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 01a2df13dd3..68d39784847 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1727,7 +1727,7 @@ ActiveRecord::Schema.define(version: 20180201145907) do
t.integer "project_id", null: false
end
- add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree
+ add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", unique: true, using: :btree
create_table "u2f_registrations", force: :cascade do |t|
t.text "certificate"
--
cgit v1.2.1
From 9b7971379e48c3c2034e53594be3c96af2a1007d Mon Sep 17 00:00:00 2001
From: Phil Hughes
Date: Fri, 2 Feb 2018 14:33:02 +0000
Subject: Backport of mr_widget_author.vue EE changes
---
.../components/mr_widget_author.vue | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
index 7ac9eadcde0..cb6e9858736 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -1,11 +1,26 @@
-