From 7be65f5d8fd7789b6f630ea04b7bcec8847ab436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 8 May 2018 12:20:54 +0200 Subject: Add cached_attr_time_reader to RedisCacheable --- app/models/concerns/redis_cacheable.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index b889f4202dc..0dd15734eae 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -12,6 +12,15 @@ module RedisCacheable end end end + + def cached_attr_time_reader(*attributes) + attributes.each do |attribute| + define_method("#{attribute}") do + cached_value = cached_attribute(attribute) + cached_value ? Time.zone.parse(cached_value) : read_attribute(attribute) + end + end + end end def cached_attribute(attribute) -- cgit v1.2.3 From 71be7a1c224813e627c9d258a32189f1949338ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 8 May 2018 12:23:07 +0200 Subject: Move Runner#contacted_at to RedisCacheable#cached_attr_time_reader --- app/models/ci/runner.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 23078f1c3ed..ce3e595a2e1 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -74,7 +74,8 @@ module Ci project_type: 3 } - cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address + cached_attr_reader :version, :revision, :platform, :architecture, :ip_address + cached_attr_time_reader :contacted_at chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout -- cgit v1.2.3 From bb3752c7d85b6f86a4ed9a92b7b3a09fc0ac9bb3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 8 May 2018 17:09:27 +0100 Subject: Fixed inconsistent spacing in web IDE sidebar Closes #46162 --- app/assets/stylesheets/pages/repo.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 00457717f00..bec7e57eacd 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -84,7 +84,6 @@ .ide-new-btn { display: none; - margin-right: -8px; } &:hover, @@ -116,7 +115,7 @@ display: flex; overflow: visible; align-items: center; - padding: 6px 12px; + padding: 6px $gl-padding; } .multi-file-loading-container { @@ -980,8 +979,8 @@ display: flex; align-items: center; padding: 10px 0; - margin-left: 10px; - margin-right: 10px; + margin-left: $gl-padding; + margin-right: $gl-padding; border-bottom: 1px solid $white-dark; .ide-new-btn { -- cgit v1.2.3 From 3d80fae582e332bc6cf547002ef236f33016dcc7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 9 May 2018 14:38:17 +0100 Subject: fixed up spacing throughout sidebars --- .../ide/components/commit_sidebar/form.vue | 6 +- .../javascripts/ide/components/repo_file.vue | 4 +- app/assets/javascripts/ide/constants.js | 2 - app/assets/stylesheets/pages/repo.scss | 87 +++++++++++++--------- 4 files changed, 56 insertions(+), 43 deletions(-) diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue index 4a645c827ad..81961fe3c57 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue @@ -5,7 +5,7 @@ import LoadingButton from '~/vue_shared/components/loading_button.vue'; import CommitMessageField from './message_field.vue'; import Actions from './actions.vue'; import SuccessMessage from './success_message.vue'; -import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT, COMMIT_ITEM_PADDING } from '../../constants'; +import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants'; export default { components: { @@ -70,7 +70,7 @@ export default { ? this.$refs.formEl && this.$refs.formEl.offsetHeight : this.$refs.compactEl && this.$refs.compactEl.offsetHeight; - this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + this.componentHeight = elHeight; }, enterTransition() { this.$nextTick(() => { @@ -78,7 +78,7 @@ export default { ? this.$refs.compactEl && this.$refs.compactEl.offsetHeight : this.$refs.formEl && this.$refs.formEl.offsetHeight; - this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + this.componentHeight = elHeight; }); }, afterEndTransition() { diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 14946f8c9fa..7bc865058c6 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -122,11 +122,11 @@ export default {
form, + > .commit-form-compact { + padding: $gl-padding 0; + margin-left: $gl-padding; + margin-right: $gl-padding; + border-top: 1px solid $white-dark; + } + .btn { font-size: $gl-font-size; } @@ -786,8 +802,9 @@ display: flex; flex: 1; flex-direction: column; - width: 100%; min-height: 140px; + margin-left: $gl-padding; + margin-right: $gl-padding; &.is-first { border-bottom: 1px solid $white-dark; @@ -979,8 +996,6 @@ display: flex; align-items: center; padding: 10px 0; - margin-left: $gl-padding; - margin-right: $gl-padding; border-bottom: 1px solid $white-dark; .ide-new-btn { @@ -1011,9 +1026,9 @@ .commit-form-slide-up-enter-active, .commit-form-slide-up-leave-active { position: absolute; - top: 16px; - left: 16px; - right: 16px; + top: 0; + left: 0; + right: 0; transition: all 0.3s ease; } -- cgit v1.2.3 From 953e1de296dc42e2f8510019413d47f43bebd609 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 9 May 2018 15:10:08 +0100 Subject: changed padding in headers --- app/assets/stylesheets/pages/repo.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 9865b65077f..0b9f9ee1ac7 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -502,7 +502,7 @@ align-items: center; margin-bottom: 0; border-bottom: 1px solid $white-dark; - padding: $grid-size 0; + padding: 12px 0; } .multi-file-commit-panel-header-title { @@ -995,7 +995,7 @@ .ide-tree-header { display: flex; align-items: center; - padding: 10px 0; + padding: 12px 0; border-bottom: 1px solid $white-dark; .ide-new-btn { -- cgit v1.2.3 From fb301b503018d5fb75f19c6c0d0f4ccf601ea70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 9 May 2018 18:38:30 +0200 Subject: Revert "Partially revert ebcd5711c5ff937bf925002bf9a5b636b037684e to fix runner pages" This reverts commit b14719ea04f29888e2bbbdccda872d3cb4e70ae7. --- app/views/admin/runners/_runner.html.haml | 2 +- app/views/shared/runners/show.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 6e76e7c2768..99fbbaec487 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -33,7 +33,7 @@ = tag %td - if runner.contacted_at - #{time_ago_in_words(runner.contacted_at)} ago + = time_ago_with_tooltip runner.contacted_at - else Never %td.admin-runner-btn-group-cell diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml index 1265305608c..1a386d96bcd 100644 --- a/app/views/shared/runners/show.html.haml +++ b/app/views/shared/runners/show.html.haml @@ -66,6 +66,6 @@ %td Last contact %td - if @runner.contacted_at - #{time_ago_in_words(@runner.contacted_at)} ago + = time_ago_with_tooltip @runner.contacted_at - else Never -- cgit v1.2.3 From 0f4aa6598add80cdd6ae2ec0aede110b89e1d325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 9 May 2018 18:55:20 +0200 Subject: Add CHANGELOG --- .../46082-runner-contacted_at-is-not-always-a-time-type.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml diff --git a/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml b/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml new file mode 100644 index 00000000000..a021884f652 --- /dev/null +++ b/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml @@ -0,0 +1,5 @@ +--- +title: Make Runner#contacted_at always a Time type. +merge_request: 18810 +author: +type: fixed -- cgit v1.2.3 From 4fd77b33186e72824861367962c524e023138aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 10 May 2018 12:38:51 +0200 Subject: Update CHANGELOG description --- .../unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml b/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml index a021884f652..07f67251b24 100644 --- a/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml +++ b/changelogs/unreleased/46082-runner-contacted_at-is-not-always-a-time-type.yml @@ -1,5 +1,5 @@ --- -title: Make Runner#contacted_at always a Time type. +title: Fix Runner contacted at tooltip cache. merge_request: 18810 author: type: fixed -- cgit v1.2.3 From 8d49ec681ffe4638f4db3311879448958d34c6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 10 May 2018 12:41:09 +0200 Subject: Use symbol instead of string in RedisCacheable attribute definitions --- app/models/concerns/redis_cacheable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index 0dd15734eae..bc86bbe6525 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -7,7 +7,7 @@ module RedisCacheable class_methods do def cached_attr_reader(*attributes) attributes.each do |attribute| - define_method("#{attribute}") do + define_method(attribute) do cached_attribute(attribute) || read_attribute(attribute) end end @@ -15,7 +15,7 @@ module RedisCacheable def cached_attr_time_reader(*attributes) attributes.each do |attribute| - define_method("#{attribute}") do + define_method(attribute) do cached_value = cached_attribute(attribute) cached_value ? Time.zone.parse(cached_value) : read_attribute(attribute) end -- cgit v1.2.3 From d3426a5f0003270af61e3b3ad38c0983d9cd4cfa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 10 May 2018 12:03:14 +0100 Subject: fixed spacing of icon & dropdown button --- app/assets/stylesheets/pages/repo.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 0b9f9ee1ac7..baef3a64be2 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -86,7 +86,6 @@ .ide-new-btn { display: none; - margin-left: auto; } &:hover, -- cgit v1.2.3 From 1736d74408a406468d9f66e4dda1a1492793b8d3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 11 May 2018 13:28:26 +0200 Subject: Improve fast specs helper to autoload the library --- spec/fast_spec_helper.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb index 978113a08a4..134eb25e4b1 100644 --- a/spec/fast_spec_helper.rb +++ b/spec/fast_spec_helper.rb @@ -3,14 +3,8 @@ require 'bundler/setup' ENV['GITLAB_ENV'] = 'test' ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true' -unless Object.respond_to?(:require_dependency) - class Object - alias_method :require_dependency, :require - end -end - -# Defines Settings and Gitlab.config which are at the center of the app require_relative '../config/settings' -require_relative '../lib/gitlab' unless defined?(Gitlab.config) - require_relative 'support/rspec' +require 'active_support/all' + +ActiveSupport::Dependencies.autoload_paths << 'lib' -- cgit v1.2.3 From 6d0c10b1b791e1938ce42c332280f5fe7dcd489f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 11 May 2018 13:28:51 +0200 Subject: Make it possible to compare untrusted regexps --- lib/gitlab/untrusted_regexp.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb index 7ce2e9d636e..fb25755391d 100644 --- a/lib/gitlab/untrusted_regexp.rb +++ b/lib/gitlab/untrusted_regexp.rb @@ -9,7 +9,7 @@ module Gitlab # there is a strict limit on total execution time. See the RE2 documentation # at https://github.com/google/re2/wiki/Syntax for more details. class UntrustedRegexp - delegate :===, to: :regexp + delegate :===, :source, to: :regexp def initialize(pattern) @regexp = RE2::Regexp.new(pattern, log_errors: false) @@ -31,6 +31,10 @@ module Gitlab RE2.Replace(text, regexp, rewrite) end + def ==(other) + self.source == other.source + end + private attr_reader :regexp -- cgit v1.2.3 From 8b736c91fc928157df9ace050f769d0948b58c1d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 11 May 2018 13:29:05 +0200 Subject: Implement variables expression untrusted pattern lexeme --- .../ci/pipeline/expression/lexeme/pattern.rb | 26 ++++++++++ .../ci/pipeline/expression/lexeme/pattern_spec.rb | 55 ++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb create mode 100644 spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb new file mode 100644 index 00000000000..8cb1af30252 --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -0,0 +1,26 @@ +module Gitlab + module Ci + module Pipeline + module Expression + module Lexeme + class Pattern < Lexeme::Value + PATTERN = %r{/(?.+)/}.freeze + + def initialize(regexp) + @value = regexp + end + + def evaluate(variables = {}) + Gitlab::UntrustedRegexp.new(@value.to_s) + # TODO raise LexerError / ParserError in case of RegexpError + end + + def self.build(string) + new(string.match(PATTERN)[:regexp]) + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb new file mode 100644 index 00000000000..b7998b512f5 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -0,0 +1,55 @@ +require 'fast_spec_helper' +require_dependency 're2' + +describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do + describe '.build' do + it 'creates a new instance of the token' do + expect(described_class.build('/.*/')) + .to be_a(described_class) + end + end + + describe '.type' do + it 'is a value lexeme' do + expect(described_class.type).to eq :value + end + end + + describe '.scan' do + it 'correctly identifies a pattern token' do + scanner = StringScanner.new('/pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('pattern') + end + + it 'is a greedy scanner for regexp boundaries' do + scanner = StringScanner.new('/some .* / pattern/') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('some .* / pattern') + end + + it 'does not allow to use an empty pattern' do + scanner = StringScanner.new(%(//)) + + token = described_class.scan(scanner) + + expect(token).to be_nil + end + end + + describe '#evaluate' do + it 'returns a regular expression' do + string = described_class.new('abc') + + expect(string.evaluate).to eq Gitlab::UntrustedRegexp.new('abc') + end + end +end -- cgit v1.2.3 From 4c2b56897e17f884f28cb8824dacb724f85f96fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 11 May 2018 18:21:17 +0200 Subject: Add specs for #cached_attr_reader and cached_attr_time_reader --- spec/models/concerns/redis_cacheable_spec.rb | 81 ++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 3d7963120b6..ca09bf75216 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -1,21 +1,28 @@ require 'spec_helper' describe RedisCacheable do - let(:model) { double } + let(:model) do + Struct.new(:id, :attributes) do + def read_attribute(attribute) + attributes[attribute] + end + end + end + + let(:payload) { { name: 'value' } } + let(:instance) { model.new(1, payload) } + let(:cache_key) { instance.__send__(:cache_attribute_key) } before do - model.extend(described_class) - allow(model).to receive(:cache_attribute_key).and_return('key') + model.include(described_class) end describe '#cached_attribute' do - let(:payload) { { attribute: 'value' } } - - subject { model.cached_attribute(payload.keys.first) } + subject { instance.cached_attribute(payload.keys.first) } it 'gets the cache attribute' do Gitlab::Redis::SharedState.with do |redis| - expect(redis).to receive(:get).with('key') + expect(redis).to receive(:get).with(cache_key) .and_return(payload.to_json) end @@ -24,16 +31,68 @@ describe RedisCacheable do end describe '#cache_attributes' do - let(:values) { { name: 'new_name' } } - - subject { model.cache_attributes(values) } + subject { instance.cache_attributes(payload) } it 'sets the cache attributes' do Gitlab::Redis::SharedState.with do |redis| - expect(redis).to receive(:set).with('key', values.to_json, anything) + expect(redis).to receive(:set).with(cache_key, payload.to_json, anything) end subject end end + + describe '#cached_attr_reader' do + subject { instance.name } + + before do + model.cached_attr_reader(:name) + end + + context 'when there is no cached value' do + it 'checks the cached value first then reads the attribute' do + expect(instance).to receive(:cached_attribute).and_return(nil) + expect(instance).to receive(:read_attribute).and_return(payload[:name]) + + expect(subject).to eq(payload[:name]) + end + end + + context 'when there is a cached value' do + it 'reads the cached value' do + expect(instance).to receive(:cached_attribute).and_return(payload[:name]) + expect(instance).not_to receive(:read_attribute) + + expect(subject).to eq(payload[:name]) + end + end + end + + describe '#cached_attr_time_reader' do + subject { instance.time } + + before do + model.cached_attr_time_reader(:time) + end + + context 'when there is no cached value' do + it 'checks the cached value first then reads the attribute' do + expect(instance).to receive(:cached_attribute).and_return(nil) + expect(instance).to receive(:read_attribute).and_return(Time.zone.now) + + expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) + expect(subject).to be_within(1.minute).of(Time.zone.now) + end + end + + context 'when there is a cached value' do + it 'reads the cached value' do + expect(instance).to receive(:cached_attribute).and_return(Time.zone.now.to_s) + expect(instance).not_to receive(:read_attribute) + + expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) + expect(subject).to be_within(1.minute).of(Time.zone.now) + end + end + end end -- cgit v1.2.3 From 20cfc3fccec75562bdb514587d2c9f7b59554c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 11 May 2018 18:36:16 +0200 Subject: Clear memoization after caching new values --- app/models/concerns/redis_cacheable.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index bc86bbe6525..8bd0df8dfbe 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -31,6 +31,8 @@ module RedisCacheable Gitlab::Redis::SharedState.with do |redis| redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME) end + + clear_memoization(:cached_attributes) end private -- cgit v1.2.3 From be73838bdfa01b7a80d087e421505dccf625ae55 Mon Sep 17 00:00:00 2001 From: Chantal Rollison Date: Fri, 11 May 2018 11:05:14 -0700 Subject: Add anchor for incoming email regex, closes 44989 --- changelogs/unreleased/ccr-incoming-email-regex-anchor.yml | 3 +++ lib/gitlab/incoming_email.rb | 2 +- spec/lib/gitlab/incoming_email_spec.rb | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/ccr-incoming-email-regex-anchor.yml diff --git a/changelogs/unreleased/ccr-incoming-email-regex-anchor.yml b/changelogs/unreleased/ccr-incoming-email-regex-anchor.yml new file mode 100644 index 00000000000..a0d787e570e --- /dev/null +++ b/changelogs/unreleased/ccr-incoming-email-regex-anchor.yml @@ -0,0 +1,3 @@ +title: Add anchor for incoming email regex +merge_request: !18917 +type: added diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index c9122a23568..d323cb9dadf 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -57,7 +57,7 @@ module Gitlab regex = Regexp.escape(wildcard_address) regex = regex.sub(Regexp.escape(WILDCARD_PLACEHOLDER), '(.+)') - Regexp.new(regex).freeze + Regexp.new(/\A#{regex}\z/).freeze end end end diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index ad087f42e06..4c0c3fcbcc7 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -83,6 +83,10 @@ describe Gitlab::IncomingEmail do it "returns reply key" do expect(described_class.key_from_address("replies+key@example.com")).to eq("key") end + + it 'does not match emails with extra bits' do + expect(described_class.key_from_address('somereplies+somekey@example.com.someotherdomain.com')).to be nil + end end context 'self.key_from_fallback_message_id' do -- cgit v1.2.3 From 544117a55b753c76ed3235aebb0a5fb808bf2cf1 Mon Sep 17 00:00:00 2001 From: Sarrah Vesselov Date: Sat, 12 May 2018 01:31:29 +0000 Subject: add specific details related to how the UX team uses labels to tackle issues --- CONTRIBUTING.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f5d19ce2ce..758c2a9ea6c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -304,7 +304,24 @@ any potential community contributor to @-mention per above. ## Implement design & UI elements -Please see the [UX Guide for GitLab]. +For guidance on UX implementation at GitLab, please refer to our [Design System](https://design.gitlab.com/). + +The UX team uses labels to manage their workflow. + +The ~"UX" label on an issue is a signal to the UX team that it will need UX attention. +To better understand the priority by which UX tackles issues, see the [UX section](https://about.gitlab.com/handbook/ux/) of the handbook. + +Once an issue has been worked on and is ready for development, a UXer applies the ~"UX ready" label to that issue. + +The UX team has a special type label called ~"design artifact". This label indicates that the final output +for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone. +Any issue labelled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is +needed until the solution has been decided. + +~"design artifact" issues are like any other issue and should contain a milestone label when scheduled in the current milestone. + +Once the ~"design artifact" issue has been completed, the UXer removes the ~"design artifact" label and applies the ~"UX ready" label. The Product Manager can use the +existing issue or decide to create a whole new issue for the purpose of development. ## Issue tracker -- cgit v1.2.3 From abdaebc8281226f1921673b0be688ecd2ce1e6a9 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Wed, 9 May 2018 04:17:38 +0900 Subject: Apply NestingDepth (level 5) (pages/pipelines.scss) --- app/assets/stylesheets/pages/pipelines.scss | 58 ++++++++-------------- .../39584-nesting-depth-5-pages-pipelines.yml | 5 ++ 2 files changed, 26 insertions(+), 37 deletions(-) create mode 100644 changelogs/unreleased/39584-nesting-depth-5-pages-pipelines.yml diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 02803e7b040..1264d977b2f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -66,13 +66,9 @@ } } - .btn-group { - &.open { - .btn-default { - background-color: $white-normal; - border-color: $border-white-normal; - } - } + .btn-group.open .btn-default { + background-color: $white-normal; + border-color: $border-white-normal; } .btn .text-center { @@ -361,16 +357,14 @@ &:not(:first-child) { margin-left: 44px; - .left-connector { - &::before { - content: ''; - position: absolute; - top: 48%; - left: -44px; - border-top: 2px solid $border-color; - width: 44px; - height: 1px; - } + .left-connector::before { + content: ''; + position: absolute; + top: 48%; + left: -44px; + border-top: 2px solid $border-color; + width: 44px; + height: 1px; } } } @@ -386,22 +380,16 @@ &:last-child { .build { // Remove right connecting horizontal line from first build in last stage - &:first-child { - &::after { - border: 0; - } + &:first-child::after { + border: 0; } // Remove right curved connectors from all builds in last stage - &:not(:first-child) { - &::after { - border: 0; - } + &:not(:first-child)::after { + border: 0; } // Remove opposite curve - .curve { - &::before { - display: none; - } + .curve::before { + display: none; } } } @@ -409,16 +397,12 @@ &:first-child { .build { // Remove left curved connectors from all builds in first stage - &:not(:first-child) { - &::before { - border: 0; - } + &:not(:first-child)::before { + border: 0; } // Remove opposite curve - .curve { - &::after { - display: none; - } + .curve::after { + display: none; } } } diff --git a/changelogs/unreleased/39584-nesting-depth-5-pages-pipelines.yml b/changelogs/unreleased/39584-nesting-depth-5-pages-pipelines.yml new file mode 100644 index 00000000000..9f07fcdfa0b --- /dev/null +++ b/changelogs/unreleased/39584-nesting-depth-5-pages-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Apply NestingDepth (level 5) (pages/pipelines.scss) +merge_request: 18830 +author: Takuya Noguchi +type: other -- cgit v1.2.3 From b1fcac85bd0f0904c277fb917790a253777d0e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 12 May 2018 14:43:05 +0200 Subject: Make #cached_attr_reader and #cached_attr_time_reader specs Redis based --- spec/models/concerns/redis_cacheable_spec.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index ca09bf75216..cb81ded7c48 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -42,7 +42,7 @@ describe RedisCacheable do end end - describe '#cached_attr_reader' do + describe '#cached_attr_reader', :clean_gitlab_redis_shared_state do subject { instance.name } before do @@ -51,7 +51,6 @@ describe RedisCacheable do context 'when there is no cached value' do it 'checks the cached value first then reads the attribute' do - expect(instance).to receive(:cached_attribute).and_return(nil) expect(instance).to receive(:read_attribute).and_return(payload[:name]) expect(subject).to eq(payload[:name]) @@ -60,15 +59,14 @@ describe RedisCacheable do context 'when there is a cached value' do it 'reads the cached value' do - expect(instance).to receive(:cached_attribute).and_return(payload[:name]) - expect(instance).not_to receive(:read_attribute) + instance.cache_attributes(payload) expect(subject).to eq(payload[:name]) end end end - describe '#cached_attr_time_reader' do + describe '#cached_attr_time_reader', :clean_gitlab_redis_shared_state do subject { instance.time } before do @@ -77,7 +75,6 @@ describe RedisCacheable do context 'when there is no cached value' do it 'checks the cached value first then reads the attribute' do - expect(instance).to receive(:cached_attribute).and_return(nil) expect(instance).to receive(:read_attribute).and_return(Time.zone.now) expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) @@ -87,8 +84,7 @@ describe RedisCacheable do context 'when there is a cached value' do it 'reads the cached value' do - expect(instance).to receive(:cached_attribute).and_return(Time.zone.now.to_s) - expect(instance).not_to receive(:read_attribute) + instance.cache_attributes(time: Time.zone.now) expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) expect(subject).to be_within(1.minute).of(Time.zone.now) -- cgit v1.2.3 From f4810647a0e962c7296ba4626b9c6b5e36662efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 12 May 2018 15:32:13 +0200 Subject: Add RedisCacheable specs for memoization correctness --- spec/models/concerns/redis_cacheable_spec.rb | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index cb81ded7c48..06d194008c1 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -9,7 +9,7 @@ describe RedisCacheable do end end - let(:payload) { { name: 'value' } } + let(:payload) { { name: 'value', time: Time.zone.now } } let(:instance) { model.new(1, payload) } let(:cache_key) { instance.__send__(:cache_attribute_key) } @@ -50,9 +50,7 @@ describe RedisCacheable do end context 'when there is no cached value' do - it 'checks the cached value first then reads the attribute' do - expect(instance).to receive(:read_attribute).and_return(payload[:name]) - + it 'reads the attribute' do expect(subject).to eq(payload[:name]) end end @@ -64,6 +62,14 @@ describe RedisCacheable do expect(subject).to eq(payload[:name]) end end + + it 'always returns the latest values' do + expect(instance.name).to eq(payload[:name]) + + instance.cache_attributes(name: 'new_value') + + expect(instance.name).to eq('new_value') + end end describe '#cached_attr_time_reader', :clean_gitlab_redis_shared_state do @@ -74,9 +80,7 @@ describe RedisCacheable do end context 'when there is no cached value' do - it 'checks the cached value first then reads the attribute' do - expect(instance).to receive(:read_attribute).and_return(Time.zone.now) - + it 'reads the attribute' do expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) expect(subject).to be_within(1.minute).of(Time.zone.now) end @@ -90,5 +94,13 @@ describe RedisCacheable do expect(subject).to be_within(1.minute).of(Time.zone.now) end end + + it 'always returns the latest values' do + expect(instance.time).to be_within(1.minute).of(Time.zone.now) + + instance.cache_attributes(time: 1.hour.ago) + + expect(instance.time).to be_within(1.minute).of(1.hour.ago) + end end end -- cgit v1.2.3 From b784a985f2188e328da32cc9fdc73c8d4ac63733 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 14 May 2018 14:24:59 +0200 Subject: Do not raise if variable expression can not be evaluated --- lib/gitlab/ci/pipeline/expression/statement.rb | 2 ++ spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb index 09a7c98464b..363e0b708a6 100644 --- a/lib/gitlab/ci/pipeline/expression/statement.rb +++ b/lib/gitlab/ci/pipeline/expression/statement.rb @@ -35,6 +35,8 @@ module Gitlab def truthful? evaluate.present? + rescue StatementError + false end def valid? diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 6685bf5385b..633c932eabb 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require 'fast_spec_helper' describe Gitlab::Ci::Pipeline::Expression::Statement do subject do @@ -114,7 +114,8 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do ['$UNDEFINED_VARIABLE == null', true], ['$PRESENT_VARIABLE', true], ['$UNDEFINED_VARIABLE', false], - ['$EMPTY_VARIABLE', false] + ['$EMPTY_VARIABLE', false], + ['$INVALID = 1', false] ] statements.each do |expression, value| @@ -126,5 +127,16 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do end end end + + context 'when evaluating expression raises an error' do + let(:text) { '$PRESENT_VARIABLE' } + + it 'returns false' do + allow(subject).to receive(:evaluate) + .and_raise(described_class::StatementError) + + expect(subject.truthful?).to be_falsey + end + end end end -- cgit v1.2.3 From ac65257c40052f739492f0648f6b7c06a1c95250 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 14 May 2018 14:38:08 +0200 Subject: Raise variables statement exception if pattern is invalid --- lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb | 6 ++++-- lib/gitlab/ci/pipeline/expression/parser.rb | 2 ++ spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb | 11 +++++++++-- spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb | 2 +- spec/lib/gitlab/ci/pipeline/expression/token_spec.rb | 2 +- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index 8cb1af30252..2ff527e34a8 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -11,8 +11,10 @@ module Gitlab end def evaluate(variables = {}) - Gitlab::UntrustedRegexp.new(@value.to_s) - # TODO raise LexerError / ParserError in case of RegexpError + # TODO multiline support + @regexp = Gitlab::UntrustedRegexp.new(@value) + rescue RegexpError + raise Parser::ParserError, 'Invalid regular expression!' end def self.build(string) diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb index 90f94d0b763..fe23ab0b2f8 100644 --- a/lib/gitlab/ci/pipeline/expression/parser.rb +++ b/lib/gitlab/ci/pipeline/expression/parser.rb @@ -3,6 +3,8 @@ module Gitlab module Pipeline module Expression class Parser + ParserError = Class.new(Statement::StatementError) + def initialize(tokens) @tokens = tokens.to_enum @nodes = [] diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index b7998b512f5..ed69742cd7c 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -47,9 +47,16 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do describe '#evaluate' do it 'returns a regular expression' do - string = described_class.new('abc') + regexp = described_class.new('abc') - expect(string.evaluate).to eq Gitlab::UntrustedRegexp.new('abc') + expect(regexp.evaluate).to eq Gitlab::UntrustedRegexp.new('abc') + end + + it 'raises error if evaluated regexp is not valid' do + regexp = described_class.new('invalid ( .*') + + expect { regexp.evaluate } + .to raise_error(Gitlab::Ci::Pipeline::Expression::Parser::ParserError) end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb index e8e6f585310..2b78b1dd4a7 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require 'fast_spec_helper' describe Gitlab::Ci::Pipeline::Expression::Parser do describe '#tree' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb index 6d7453f0de5..cedfe270f9d 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require 'fast_spec_helper' describe Gitlab::Ci::Pipeline::Expression::Token do let(:value) { '$VARIABLE' } -- cgit v1.2.3 From f16f2b599412ed1514ba96d81758b9a2e6fd9c1f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 14 May 2018 15:03:10 +0200 Subject: Add pattern matching variables expression lexeme --- .../ci/pipeline/expression/lexeme/matches.rb | 29 +++++++++++ .../ci/pipeline/expression/lexeme/matches_spec.rb | 57 ++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 lib/gitlab/ci/pipeline/expression/lexeme/matches.rb create mode 100644 spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb new file mode 100644 index 00000000000..806f2082227 --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb @@ -0,0 +1,29 @@ +module Gitlab + module Ci + module Pipeline + module Expression + module Lexeme + class Matches < Lexeme::Operator + PATTERN = /=~/.freeze + + def initialize(left, right) + @left = left + @right = right + end + + def evaluate(variables = {}) + text = @left.evaluate(variables) + regexp = @right.evaluate(variables) + + regexp.scan(text).any? + end + + def self.build(_value, behind, ahead) + new(behind, ahead) + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb new file mode 100644 index 00000000000..22907b0554a --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -0,0 +1,57 @@ +require 'fast_spec_helper' +require_dependency 're2' + +describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do + let(:left) { double('left') } + let(:right) { double('right') } + + describe '.build' do + it 'creates a new instance of the token' do + expect(described_class.build('=~', left, right)) + .to be_a(described_class) + end + end + + describe '.type' do + it 'is an operator' do + expect(described_class.type).to eq :operator + end + end + + describe '#evaluate' do + it 'returns false when left and right do not match' do + allow(left).to receive(:evaluate).and_return('my-string') + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('something')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq false + end + + it 'returns true when left and right match' do + allow(left).to receive(:evaluate).and_return('my-awesome-string') + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('awesome.string$')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq true + end + + it 'supports multiline strings' do + allow(left).to receive(:evaluate).and_return <<~TEXT + My awesome contents + + My-text-string! + TEXT + + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('text-string')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq true + end + end +end -- cgit v1.2.3 From 13f68f55b31847fc8fab03ce3aadf92c930dd531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 14 May 2018 15:54:23 +0200 Subject: Expect calls to read_attribute depending on cache presence --- spec/models/concerns/redis_cacheable_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 06d194008c1..bd5319a0c8c 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -51,12 +51,16 @@ describe RedisCacheable do context 'when there is no cached value' do it 'reads the attribute' do + expect(instance).to receive(:read_attribute).and_call_original + expect(subject).to eq(payload[:name]) end end context 'when there is a cached value' do it 'reads the cached value' do + expect(instance).not_to receive(:read_attribute) + instance.cache_attributes(payload) expect(subject).to eq(payload[:name]) @@ -81,6 +85,8 @@ describe RedisCacheable do context 'when there is no cached value' do it 'reads the attribute' do + expect(instance).to receive(:read_attribute).and_call_original + expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) expect(subject).to be_within(1.minute).of(Time.zone.now) end @@ -88,6 +94,8 @@ describe RedisCacheable do context 'when there is a cached value' do it 'reads the cached value' do + expect(instance).not_to receive(:read_attribute) + instance.cache_attributes(time: Time.zone.now) expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) -- cgit v1.2.3 From 2c29e80a93dffb1a854f4c63171b1a91eddea8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 14 May 2018 16:12:18 +0200 Subject: Check for exact time matches --- spec/models/concerns/redis_cacheable_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index bd5319a0c8c..2e496cfb439 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -79,6 +79,8 @@ describe RedisCacheable do describe '#cached_attr_time_reader', :clean_gitlab_redis_shared_state do subject { instance.time } + let(:other_time) { Time.zone.parse('May 14 2018') } + before do model.cached_attr_time_reader(:time) end @@ -88,7 +90,7 @@ describe RedisCacheable do expect(instance).to receive(:read_attribute).and_call_original expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) - expect(subject).to be_within(1.minute).of(Time.zone.now) + expect(subject).to eq(payload[:time]) end end @@ -96,19 +98,19 @@ describe RedisCacheable do it 'reads the cached value' do expect(instance).not_to receive(:read_attribute) - instance.cache_attributes(time: Time.zone.now) + instance.cache_attributes(time: other_time) expect(subject).to be_instance_of(ActiveSupport::TimeWithZone) - expect(subject).to be_within(1.minute).of(Time.zone.now) + expect(subject).to eq(other_time) end end it 'always returns the latest values' do - expect(instance.time).to be_within(1.minute).of(Time.zone.now) + expect(instance.time).to eq(payload[:time]) - instance.cache_attributes(time: 1.hour.ago) + instance.cache_attributes(time: other_time) - expect(instance.time).to be_within(1.minute).of(1.hour.ago) + expect(instance.time).to eq(other_time) end end end -- cgit v1.2.3 From 1fa1858546af5ca0339de9b0fef77d739496c7d9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 14 May 2018 18:29:21 +0100 Subject: Replace vue resource with axios for environments --- .../environments/components/environments_app.vue | 3 +- .../environments/mixins/environments_mixin.js | 20 +- .../environments/services/environments_service.js | 15 +- .../environments/environments_app_spec.js | 212 ++++++++------------- .../folder/environments_folder_view_spec.js | 81 +++----- spec/javascripts/environments/mock_data.js | 1 + 6 files changed, 116 insertions(+), 216 deletions(-) diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index c0be72f7401..3da762446c9 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -68,8 +68,7 @@ this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader); this.service.getFolderContent(folder.folder_path) - .then(resp => resp.json()) - .then(response => this.store.setfolderContent(folder, response.environments)) + .then(response => this.store.setfolderContent(folder, response.data.environments)) .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false)) .catch(() => { Flash(s__('Environments|An error occurred while fetching the environments.')); diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 34d18d55120..c8745e35802 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -6,7 +6,6 @@ import Visibility from 'visibilityjs'; import Poll from '../../lib/utils/poll'; import { getParameterByName, - parseQueryStringIntoObject, } from '../../lib/utils/common_utils'; import { s__ } from '../../locale'; import Flash from '../../flash'; @@ -46,17 +45,14 @@ export default { methods: { saveData(resp) { - const headers = resp.headers; - return resp.json().then((response) => { - this.isLoading = false; - - if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) { - this.store.storeAvailableCount(response.available_count); - this.store.storeStoppedCount(response.stopped_count); - this.store.storeEnvironments(response.environments); - this.store.setPagination(headers); - } - }); + this.isLoading = false; + + if (_.isEqual(resp.config.params, this.requestData)) { + this.store.storeAvailableCount(resp.data.available_count); + this.store.storeStoppedCount(resp.data.stopped_count); + this.store.storeEnvironments(resp.data.environments); + this.store.setPagination(resp.headers); + } }, /** diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js index 03ab74b3338..22563fad8d2 100644 --- a/app/assets/javascripts/environments/services/environments_service.js +++ b/app/assets/javascripts/environments/services/environments_service.js @@ -1,25 +1,22 @@ -/* eslint-disable class-methods-use-this */ -import Vue from 'vue'; -import VueResource from 'vue-resource'; - -Vue.use(VueResource); +import axios from '~/lib/utils/axios_utils'; export default class EnvironmentsService { constructor(endpoint) { - this.environments = Vue.resource(endpoint); + this.environmentsEndpoint = endpoint; this.folderResults = 3; } get(options = {}) { const { scope, page } = options; - return this.environments.get({ scope, page }); + return axios.get(this.environmentsEndpoint, { params: { scope, page } }); } + // eslint-disable-next-line class-methods-use-this postAction(endpoint) { - return Vue.http.post(endpoint, {}, { emulateJSON: true }); + return axios.post(endpoint, {}, { emulateJSON: true }); } getFolderContent(folderUrl) { - return Vue.http.get(`${folderUrl}.json?per_page=${this.folderResults}`); + return axios.get(`${folderUrl}.json?per_page=${this.folderResults}`); } } diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index e4c3bf2bef1..615168645b4 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -1,8 +1,8 @@ -import _ from 'underscore'; import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import environmentsComponent from '~/environments/components/environments_app.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; import { environment, folder } from './mock_data'; describe('Environment', () => { @@ -18,103 +18,76 @@ describe('Environment', () => { let EnvironmentsComponent; let component; + let mock; beforeEach(() => { + mock = new MockAdapter(axios); + EnvironmentsComponent = Vue.extend(environmentsComponent); }); + afterEach(() => { + component.$destroy(); + mock.restore(); + }); + describe('successfull request', () => { describe('without environments', () => { - const environmentsEmptyResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 200, - })); - }; - - beforeEach(() => { - Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); - }); - - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsEmptyResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); + beforeEach((done) => { + mock.onGet(mockData.endpoint).reply(200, { environments: [] }); - it('should render the empty state', (done) => { component = mountComponent(EnvironmentsComponent, mockData); setTimeout(() => { - expect( - component.$el.querySelector('.js-new-environment-button').textContent, - ).toContain('New environment'); - - expect( - component.$el.querySelector('.js-blank-state-title').textContent, - ).toContain('You don\'t have any environments right now.'); - done(); }, 0); }); + + it('should render the empty state', () => { + expect( + component.$el.querySelector('.js-new-environment-button').textContent, + ).toContain('New environment'); + + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + }); }); describe('with paginated environments', () => { - let backupInterceptors; - const environmentsResponseInterceptor = (request, next) => { - next((response) => { - response.headers.set('X-nExt-pAge', '2'); - }); - - next(request.respondWith(JSON.stringify({ + beforeEach((done) => { + mock.onGet(mockData.endpoint).reply(200, { environments: [environment], stopped_count: 1, available_count: 0, - }), { - status: 200, - headers: { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', - }, - })); - }; - - beforeEach(() => { - backupInterceptors = Vue.http.interceptors; - Vue.http.interceptors = [ - environmentsResponseInterceptor, - headersInterceptor, - ]; - component = mountComponent(EnvironmentsComponent, mockData); - }); + }, { + 'X-nExt-pAge': '2', + 'x-page': '1', + 'X-Per-Page': '1', + 'X-Prev-Page': '', + 'X-TOTAL': '37', + 'X-Total-Pages': '2', + }); - afterEach(() => { - Vue.http.interceptors = backupInterceptors; - }); + component = mountComponent(EnvironmentsComponent, mockData); - it('should render a table with environments', (done) => { setTimeout(() => { - expect(component.$el.querySelectorAll('table')).not.toBeNull(); - expect( - component.$el.querySelector('.environment-name').textContent.trim(), - ).toEqual(environment.name); done(); }, 0); }); + it('should render a table with environments', () => { + expect(component.$el.querySelectorAll('table')).not.toBeNull(); + expect( + component.$el.querySelector('.environment-name').textContent.trim(), + ).toEqual(environment.name); + }); + describe('pagination', () => { - it('should render pagination', (done) => { - setTimeout(() => { - expect( - component.$el.querySelectorAll('.gl-pagination li').length, - ).toEqual(5); - done(); - }, 0); + it('should render pagination', () => { + expect( + component.$el.querySelectorAll('.gl-pagination li').length, + ).toEqual(5); }); it('should make an API request when page is clicked', (done) => { @@ -133,50 +106,39 @@ describe('Environment', () => { expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); done(); - }); + }, 0); }); }); }); }); describe('unsuccessfull request', () => { - const environmentsErrorResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 500, - })); - }; - - beforeEach(() => { - Vue.http.interceptors.push(environmentsErrorResponseInterceptor); - }); + beforeEach((done) => { + mock.onGet(mockData.endpoint).reply(500, {}); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsErrorResponseInterceptor, - ); - }); - - it('should render empty state', (done) => { component = mountComponent(EnvironmentsComponent, mockData); setTimeout(() => { - expect( - component.$el.querySelector('.js-blank-state-title').textContent, - ).toContain('You don\'t have any environments right now.'); done(); }, 0); }); + + it('should render empty state', () => { + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + }); }); describe('expandable folders', () => { - const environmentsResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ - environments: [folder], - stopped_count: 0, - available_count: 1, - }), { - status: 200, - headers: { + beforeEach(() => { + mock.onGet(mockData.endpoint).reply(200, + { + environments: [folder], + stopped_count: 0, + available_count: 1, + }, + { 'X-nExt-pAge': '2', 'x-page': '1', 'X-Per-Page': '1', @@ -184,18 +146,11 @@ describe('Environment', () => { 'X-TOTAL': '37', 'X-Total-Pages': '2', }, - })); - }; + ); - beforeEach(() => { - Vue.http.interceptors.push(environmentsResponseInterceptor); - component = mountComponent(EnvironmentsComponent, mockData); - }); + mock.onGet(environment.folder_path).reply(200, { environments: [environment] }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsResponseInterceptor, - ); + component = mountComponent(EnvironmentsComponent, mockData); }); it('should open a closed folder', (done) => { @@ -211,7 +166,7 @@ describe('Environment', () => { ).not.toContain('display: none'); done(); }); - }); + }, 0); }); it('should close an opened folder', (done) => { @@ -233,7 +188,7 @@ describe('Environment', () => { done(); }); }); - }); + }, 0); }); it('should show children environments and a button to show all environments', (done) => { @@ -242,49 +197,32 @@ describe('Environment', () => { component.$el.querySelector('.folder-name').click(); Vue.nextTick(() => { - const folderInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ - environments: [environment], - }), { status: 200 })); - }; - - Vue.http.interceptors.push(folderInterceptor); - // wait for next async request setTimeout(() => { expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1); expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain('Show all'); - - Vue.http.interceptors = _.without(Vue.http.interceptors, folderInterceptor); done(); }); }); - }); + }, 0); }); }); describe('methods', () => { - const environmentsEmptyResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 200, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); + mock.onGet(mockData.endpoint).reply(200, + { + environments: [], + stopped_count: 0, + available_count: 1, + }, + {}, + ); component = mountComponent(EnvironmentsComponent, mockData); spyOn(history, 'pushState').and.stub(); }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsEmptyResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); - describe('updateContent', () => { it('should set given parameters', (done) => { component.updateContent({ scope: 'stopped', page: '3' }) diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 906a1116974..f5ce4df0bfe 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -1,13 +1,15 @@ -import _ from 'underscore'; import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue'; -import { headersInterceptor } from 'spec/helpers/vue_resource_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; import { environmentsList } from '../mock_data'; describe('Environments Folder View', () => { let Component; let component; + let mock; + const mockData = { endpoint: 'environments.json', folderName: 'review', @@ -17,46 +19,35 @@ describe('Environments Folder View', () => { }; beforeEach(() => { + mock = new MockAdapter(axios); + Component = Vue.extend(environmentsFolderViewComponent); }); afterEach(() => { + mock.restore(); + component.$destroy(); }); describe('successfull request', () => { - const environmentsResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ + beforeEach(() => { + mock.onGet(mockData.endpoint).reply(200, { environments: environmentsList, stopped_count: 1, available_count: 0, - }), { - status: 200, - headers: { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '2', - 'X-Prev-Page': '', - 'X-TOTAL': '20', - 'X-Total-Pages': '10', - }, - })); - }; - - beforeEach(() => { - Vue.http.interceptors.push(environmentsResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); + }, { + 'X-nExt-pAge': '2', + 'x-page': '1', + 'X-Per-Page': '2', + 'X-Prev-Page': '', + 'X-TOTAL': '20', + 'X-Total-Pages': '10', + }); component = mountComponent(Component, mockData); }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); - it('should render a table with environments', (done) => { setTimeout(() => { expect(component.$el.querySelectorAll('table')).not.toBeNull(); @@ -135,25 +126,15 @@ describe('Environments Folder View', () => { }); describe('unsuccessfull request', () => { - const environmentsErrorResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 500, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(environmentsErrorResponseInterceptor); - }); + mock.onGet(mockData.endpoint).reply(500, { + environments: [], + }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsErrorResponseInterceptor, - ); + component = mountComponent(Component, mockData); }); it('should not render a table', (done) => { - component = mountComponent(Component, mockData); - setTimeout(() => { expect( component.$el.querySelector('table'), @@ -190,27 +171,15 @@ describe('Environments Folder View', () => { }); describe('methods', () => { - const environmentsEmptyResponseInterceptor = (request, next) => { - next(request.respondWith(JSON.stringify([]), { - status: 200, - })); - }; - beforeEach(() => { - Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); + mock.onGet(mockData.endpoint).reply(200, { + environments: [], + }); component = mountComponent(Component, mockData); spyOn(history, 'pushState').and.stub(); }); - afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsEmptyResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); - }); - describe('updateContent', () => { it('should set given parameters', (done) => { component.updateContent({ scope: 'stopped', page: '4' }) diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js index 15e11aa686b..8a1e26935d9 100644 --- a/spec/javascripts/environments/mock_data.js +++ b/spec/javascripts/environments/mock_data.js @@ -82,6 +82,7 @@ export const environment = { stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', updated_at: '2017-01-31T10:53:46.894Z', + folder_path: '/root/review-app/environments/7', }, }; -- cgit v1.2.3 From 475d2edf04c13c6a5ed2b5c68d75efd2a8024c2b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 12:37:09 +0200 Subject: Reorganize exceptions in pipeline expressions module --- lib/gitlab/ci/pipeline/expression.rb | 10 ++++++++++ lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb | 3 +-- lib/gitlab/ci/pipeline/expression/lexer.rb | 4 ++-- lib/gitlab/ci/pipeline/expression/parser.rb | 2 -- lib/gitlab/ci/pipeline/expression/statement.rb | 6 +++--- spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb | 2 +- spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb | 8 ++++---- spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb | 4 ++-- 8 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 lib/gitlab/ci/pipeline/expression.rb diff --git a/lib/gitlab/ci/pipeline/expression.rb b/lib/gitlab/ci/pipeline/expression.rb new file mode 100644 index 00000000000..f57df7c5637 --- /dev/null +++ b/lib/gitlab/ci/pipeline/expression.rb @@ -0,0 +1,10 @@ +module Gitlab + module Ci + module Pipeline + module Expression + ExpressionError = Class.new(StandardError) + RuntimeError = Class.new(ExpressionError) + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index 2ff527e34a8..59b8e4fad4c 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -11,10 +11,9 @@ module Gitlab end def evaluate(variables = {}) - # TODO multiline support @regexp = Gitlab::UntrustedRegexp.new(@value) rescue RegexpError - raise Parser::ParserError, 'Invalid regular expression!' + raise Expression::RuntimeError, 'Invalid regular expression!' end def self.build(string) diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb index e1c68b7c3c2..ebc6565266f 100644 --- a/lib/gitlab/ci/pipeline/expression/lexer.rb +++ b/lib/gitlab/ci/pipeline/expression/lexer.rb @@ -5,6 +5,8 @@ module Gitlab class Lexer include ::Gitlab::Utils::StrongMemoize + SyntaxError = Class.new(Expression::ExpressionError) + LEXEMES = [ Expression::Lexeme::Variable, Expression::Lexeme::String, @@ -12,8 +14,6 @@ module Gitlab Expression::Lexeme::Equals ].freeze - SyntaxError = Class.new(Statement::StatementError) - MAX_TOKENS = 100 def initialize(statement, max_tokens: MAX_TOKENS) diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb index fe23ab0b2f8..90f94d0b763 100644 --- a/lib/gitlab/ci/pipeline/expression/parser.rb +++ b/lib/gitlab/ci/pipeline/expression/parser.rb @@ -3,8 +3,6 @@ module Gitlab module Pipeline module Expression class Parser - ParserError = Class.new(Statement::StatementError) - def initialize(tokens) @tokens = tokens.to_enum @nodes = [] diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb index 363e0b708a6..de37c50d2a2 100644 --- a/lib/gitlab/ci/pipeline/expression/statement.rb +++ b/lib/gitlab/ci/pipeline/expression/statement.rb @@ -3,7 +3,7 @@ module Gitlab module Pipeline module Expression class Statement - StatementError = Class.new(StandardError) + StatementError = Class.new(Expression::ExpressionError) GRAMMAR = [ %w[variable equals string], @@ -35,13 +35,13 @@ module Gitlab def truthful? evaluate.present? - rescue StatementError + rescue Expression::ExpressionError false end def valid? parse_tree.is_a?(Lexeme::Base) - rescue StatementError + rescue Expression::ExpressionError false end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index ed69742cd7c..47385ce0a5b 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -56,7 +56,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do regexp = described_class.new('invalid ( .*') expect { regexp.evaluate } - .to raise_error(Gitlab::Ci::Pipeline::Expression::Parser::ParserError) + .to raise_error(Gitlab::Ci::Pipeline::Expression::RuntimeError) end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb index 230ceeb07f8..3f11b3f7673 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do end describe '#tokens' do - it 'tokenss single value' do + it 'returns single value' do tokens = described_class.new('$VARIABLE').tokens expect(tokens).to be_one @@ -20,14 +20,14 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do expect(tokens).to all(be_an_instance_of(token_class)) end - it 'tokenss multiple values of the same token' do + it 'returns multiple values of the same token' do tokens = described_class.new("$VARIABLE1 $VARIABLE2").tokens expect(tokens.size).to eq 2 expect(tokens).to all(be_an_instance_of(token_class)) end - it 'tokenss multiple values with different tokens' do + it 'returns multiple values with different tokens' do tokens = described_class.new('$VARIABLE "text" "value"').tokens expect(tokens.size).to eq 3 @@ -36,7 +36,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do expect(tokens.third.value).to eq '"value"' end - it 'tokenss tokens and operators' do + it 'returns tokens and operators' do tokens = described_class.new('$VARIABLE == "text"').tokens expect(tokens.size).to eq 3 diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 633c932eabb..6d58838bf14 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -36,7 +36,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do '== "123"', # invalid left side '"some string"', # only string provided '$VAR ==', # invalid right side - '12345', # unknown syntax + 'null', # missing lexemes '' # empty statement ] @@ -44,7 +44,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do context "when expression grammar is #{syntax.inspect}" do let(:text) { syntax } - it 'aises a statement error exception' do + it 'raises a statement error exception' do expect { subject.parse_tree } .to raise_error described_class::StatementError end -- cgit v1.2.3 From 65f4e7b2a1fe8946109c7aa0d59999bfaaeba257 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 13:04:18 +0200 Subject: Add support for pattern matching in variables expressions --- lib/gitlab/ci/pipeline/expression/lexeme/matches.rb | 2 +- lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb | 2 ++ lib/gitlab/ci/pipeline/expression/lexer.rb | 4 +++- lib/gitlab/ci/pipeline/expression/statement.rb | 4 +++- spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb | 10 ++++++++++ spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb | 1 - spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb | 11 ++++++++--- 7 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb index 806f2082227..10957598f76 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb @@ -15,7 +15,7 @@ module Gitlab text = @left.evaluate(variables) regexp = @right.evaluate(variables) - regexp.scan(text).any? + regexp.scan(text.to_s).any? end def self.build(_value, behind, ahead) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index 59b8e4fad4c..f7b12914249 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -3,6 +3,8 @@ module Gitlab module Pipeline module Expression module Lexeme + require_dependency 're2' + class Pattern < Lexeme::Value PATTERN = %r{/(?.+)/}.freeze diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb index ebc6565266f..4cacb1e62c9 100644 --- a/lib/gitlab/ci/pipeline/expression/lexer.rb +++ b/lib/gitlab/ci/pipeline/expression/lexer.rb @@ -10,8 +10,10 @@ module Gitlab LEXEMES = [ Expression::Lexeme::Variable, Expression::Lexeme::String, + Expression::Lexeme::Pattern, Expression::Lexeme::Null, - Expression::Lexeme::Equals + Expression::Lexeme::Equals, + Expression::Lexeme::Matches ].freeze MAX_TOKENS = 100 diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb index de37c50d2a2..8886cbae516 100644 --- a/lib/gitlab/ci/pipeline/expression/statement.rb +++ b/lib/gitlab/ci/pipeline/expression/statement.rb @@ -6,12 +6,14 @@ module Gitlab StatementError = Class.new(Expression::ExpressionError) GRAMMAR = [ + %w[variable], %w[variable equals string], %w[variable equals variable], %w[variable equals null], %w[string equals variable], %w[null equals variable], - %w[variable] + %w[variable matches pattern], + %w[pattern matches variable] ].freeze def initialize(statement, variables = {}) diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb index 22907b0554a..a8890262402 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -39,6 +39,16 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do expect(operator.evaluate).to eq true end + it 'supports matching against a nil value' do + allow(left).to receive(:evaluate).and_return(nil) + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('pattern')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq false + end + it 'supports multiline strings' do allow(left).to receive(:evaluate).and_return <<~TEXT My awesome contents diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index 47385ce0a5b..a14a28056d8 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -1,5 +1,4 @@ require 'fast_spec_helper' -require_dependency 're2' describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do describe '.build' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 6d58838bf14..a5733c13768 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -84,7 +84,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do describe '#evaluate' do statements = [ ['$PRESENT_VARIABLE == "my variable"', true], - ["$PRESENT_VARIABLE == 'my variable'", true], ['"my variable" == $PRESENT_VARIABLE', true], ['$PRESENT_VARIABLE == null', false], ['$EMPTY_VARIABLE == null', false], @@ -93,7 +92,11 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do ['$UNDEFINED_VARIABLE == null', true], ['null == $UNDEFINED_VARIABLE', true], ['$PRESENT_VARIABLE', 'my variable'], - ['$UNDEFINED_VARIABLE', nil] + ['$UNDEFINED_VARIABLE', nil], + ["$PRESENT_VARIABLE =~ /var.*e$/", true], + ["$PRESENT_VARIABLE =~ /^var.*/", false], + ["$EMPTY_VARIABLE =~ /var.*/", false], + ["$UNDEFINED_VARIABLE =~ /var.*/", false] ] statements.each do |expression, value| @@ -115,7 +118,9 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do ['$PRESENT_VARIABLE', true], ['$UNDEFINED_VARIABLE', false], ['$EMPTY_VARIABLE', false], - ['$INVALID = 1', false] + ['$INVALID = 1', false], + ["$PRESENT_VARIABLE =~ /var.*/", true], + ["$UNDEFINED_VARIABLE =~ /var.*/", false] ] statements.each do |expression, value| -- cgit v1.2.3 From 7babc59e4713e53162ac028eb570b78988d3bd6c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 13:15:38 +0200 Subject: Use parameterized RSpec to improve variables expressions specs --- .../ci/pipeline/expression/statement_spec.rb | 87 +++++++++++----------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index a5733c13768..bba5db7904a 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -1,4 +1,5 @@ require 'fast_spec_helper' +require 'rspec-parameterized' describe Gitlab::Ci::Pipeline::Expression::Statement do subject do @@ -82,54 +83,54 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do end describe '#evaluate' do - statements = [ - ['$PRESENT_VARIABLE == "my variable"', true], - ['"my variable" == $PRESENT_VARIABLE', true], - ['$PRESENT_VARIABLE == null', false], - ['$EMPTY_VARIABLE == null', false], - ['"" == $EMPTY_VARIABLE', true], - ['$EMPTY_VARIABLE', ''], - ['$UNDEFINED_VARIABLE == null', true], - ['null == $UNDEFINED_VARIABLE', true], - ['$PRESENT_VARIABLE', 'my variable'], - ['$UNDEFINED_VARIABLE', nil], - ["$PRESENT_VARIABLE =~ /var.*e$/", true], - ["$PRESENT_VARIABLE =~ /^var.*/", false], - ["$EMPTY_VARIABLE =~ /var.*/", false], - ["$UNDEFINED_VARIABLE =~ /var.*/", false] - ] - - statements.each do |expression, value| - context "when using expression `#{expression}`" do - let(:text) { expression } - - it "evaluates to `#{value.inspect}`" do - expect(subject.evaluate).to eq value - end + using RSpec::Parameterized::TableSyntax + + where(:expression, :value) do + '$PRESENT_VARIABLE == "my variable"' | true + '"my variable" == $PRESENT_VARIABLE' | true + '$PRESENT_VARIABLE == null' | false + '$EMPTY_VARIABLE == null' | false + '"" == $EMPTY_VARIABLE' | true + '$EMPTY_VARIABLE' | '' + '$UNDEFINED_VARIABLE == null' | true + 'null == $UNDEFINED_VARIABLE' | true + '$PRESENT_VARIABLE' | 'my variable' + '$UNDEFINED_VARIABLE' | nil + "$PRESENT_VARIABLE =~ /var.*e$/" | true + "$PRESENT_VARIABLE =~ /^var.*/" | false + "$EMPTY_VARIABLE =~ /var.*/" | false + "$UNDEFINED_VARIABLE =~ /var.*/" | false + end + + with_them do + let(:text) { expression } + + it "evaluates to `#{params[:value].inspect}`" do + expect(subject.evaluate).to eq value end end end describe '#truthful?' do - statements = [ - ['$PRESENT_VARIABLE == "my variable"', true], - ["$PRESENT_VARIABLE == 'no match'", false], - ['$UNDEFINED_VARIABLE == null', true], - ['$PRESENT_VARIABLE', true], - ['$UNDEFINED_VARIABLE', false], - ['$EMPTY_VARIABLE', false], - ['$INVALID = 1', false], - ["$PRESENT_VARIABLE =~ /var.*/", true], - ["$UNDEFINED_VARIABLE =~ /var.*/", false] - ] - - statements.each do |expression, value| - context "when using expression `#{expression}`" do - let(:text) { expression } - - it "returns `#{value.inspect}`" do - expect(subject.truthful?).to eq value - end + using RSpec::Parameterized::TableSyntax + + where(:expression, :value) do + '$PRESENT_VARIABLE == "my variable"' | true + "$PRESENT_VARIABLE == 'no match'" | false + '$UNDEFINED_VARIABLE == null' | true + '$PRESENT_VARIABLE' | true + '$UNDEFINED_VARIABLE' | false + '$EMPTY_VARIABLE' | false + '$INVALID = 1' | false + "$PRESENT_VARIABLE =~ /var.*/" | true + "$UNDEFINED_VARIABLE =~ /var.*/" | false + end + + with_them do + let(:text) { expression } + + it "returns `#{params[:value].inspect}`" do + expect(subject.truthful?).to eq value end end -- cgit v1.2.3 From df91580cc400897c5bf69d5e496b2e4db2f75e4f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 13:31:45 +0200 Subject: Do not support inverse variable pattern matching --- lib/gitlab/ci/pipeline/expression/statement.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb index 8886cbae516..b36f1e0f865 100644 --- a/lib/gitlab/ci/pipeline/expression/statement.rb +++ b/lib/gitlab/ci/pipeline/expression/statement.rb @@ -12,8 +12,7 @@ module Gitlab %w[variable equals null], %w[string equals variable], %w[null equals variable], - %w[variable matches pattern], - %w[pattern matches variable] + %w[variable matches pattern] ].freeze def initialize(statement, variables = {}) -- cgit v1.2.3 From 9b213583937d73c79115a043b7733c76f79f5d3c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 13:40:17 +0200 Subject: Add variables expressions regexp support changelog --- changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml diff --git a/changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml b/changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml new file mode 100644 index 00000000000..d77c5b42497 --- /dev/null +++ b/changelogs/unreleased/feature-gb-add-regexp-variables-expression.yml @@ -0,0 +1,5 @@ +--- +title: Add support for variables expression pattern matching syntax +merge_request: 18902 +author: +type: added -- cgit v1.2.3 From c1377c6cf0647cccd04bc6fb65d745a2e446f827 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 14:35:12 +0200 Subject: Remove useless assignment in pattern lexeme --- lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index f7b12914249..62927441035 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -13,7 +13,7 @@ module Gitlab end def evaluate(variables = {}) - @regexp = Gitlab::UntrustedRegexp.new(@value) + Gitlab::UntrustedRegexp.new(@value) rescue RegexpError raise Expression::RuntimeError, 'Invalid regular expression!' end -- cgit v1.2.3 From 73aee958e2f4f7428a961c3c63323a7b782599bf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 14:41:20 +0200 Subject: Add docs on pattern matching syntax in variables expression --- doc/ci/variables/README.md | 7 +++++++ doc/ci/yaml/README.md | 13 ++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 42367bf13f7..cbd2ab979f4 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -530,6 +530,13 @@ Below you can find supported syntax reference: `$STAGING` value needs to a string, with length higher than zero. Variable that contains only whitespace characters is not an empty variable. +1. Pattern matching _(added in 11.0)_ + + > Example: `$VARIABLE =~ /^content.*/` + + It is possible perform pattern matching against a variable and regular + expression. Expression like this evaluates to truth if matches are found. + ### Unsupported predefined variables Because GitLab evaluates variables before creating jobs, we do not support a diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 2a17a51d7f8..3e77a6f58b7 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -344,10 +344,11 @@ job: kubernetes: active ``` -Example of using variables expressions: +Examples of using variables expressions: ```yaml deploy: + script: cap staging deploy only: refs: - branches @@ -356,6 +357,16 @@ deploy: - $STAGING ``` +Another use case is exluding jobs depending on a commit message _(added in 11.0)_: + +```yaml +end-to-end: + script: rake test:end-to-end + except: + variables: + - $CI_COMMIT_MESSAGE =~ /skip-end-to-end-tests/ +``` + Learn more about variables expressions on [a separate page][variables-expressions]. ## `tags` -- cgit v1.2.3 From f52de2f73cc9d26c26fd66c23892ac42bf973b05 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 15 May 2018 15:18:18 +0200 Subject: Make variables expression pattern case-sensitivity explicit --- doc/ci/variables/README.md | 8 ++++++++ .../gitlab/ci/pipeline/expression/lexeme/matches_spec.rb | 13 +++++++++++++ spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb | 1 + 3 files changed, 22 insertions(+) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index cbd2ab979f4..58acc030aee 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -537,6 +537,12 @@ Below you can find supported syntax reference: It is possible perform pattern matching against a variable and regular expression. Expression like this evaluates to truth if matches are found. + Pattern matching is case-sensitive by default. Prepend `(?i)` to a + match-group to make a pattern case-insensitive. + + Under the hood we are using [RE2 library][re2-library], see + [syntax documentation][re2-syntax] for reference. + ### Unsupported predefined variables Because GitLab evaluates variables before creating jobs, we do not support a @@ -577,3 +583,5 @@ These variables are also not supported in a context of a [builds-policies]: ../yaml/README.md#only-and-except-complex [dynamic-environments]: ../environments.md#dynamic-environments [gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token +[re2-library]: https://github.com/google/re2 +[re2-syntax]: https://github.com/google/re2/wiki/Syntax diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb index a8890262402..49e5af52f4d 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -63,5 +63,18 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do expect(operator.evaluate).to eq true end + + it 'supports regexp flags' do + allow(left).to receive(:evaluate).and_return <<~TEXT + My AWESOME content + TEXT + + allow(right).to receive(:evaluate) + .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome')) + + operator = described_class.new(left, right) + + expect(operator.evaluate).to eq true + end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index bba5db7904a..1ceb373e19c 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -100,6 +100,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do "$PRESENT_VARIABLE =~ /^var.*/" | false "$EMPTY_VARIABLE =~ /var.*/" | false "$UNDEFINED_VARIABLE =~ /var.*/" | false + "$PRESENT_VARIABLE =~ /(?i)VAR.*/" | true end with_them do -- cgit v1.2.3 From 3593b83a0279bab40f8ba97dc339b32c56f6e0df Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 15 May 2018 16:55:07 +0100 Subject: Handles action icons requests in a contained way and shows a loading icon to the user --- .../components/graph/action_component.vue | 67 ++++++++++++-------- .../components/graph/dropdown_job_component.vue | 13 ++-- .../pipelines/components/graph/graph_component.vue | 11 ++-- .../pipelines/components/graph/job_component.vue | 12 ++-- .../components/graph/stage_column_component.vue | 13 ++-- .../pipelines/components/pipelines_table_row.vue | 1 + .../javascripts/pipelines/components/stage.vue | 17 ++++++ .../pipelines/pipeline_details_bundle.js | 30 +++------ .../pipelines/graph/action_component_spec.js | 71 +++++++++------------- spec/javascripts/pipelines/stage_spec.js | 49 +++++++++++++++ 10 files changed, 171 insertions(+), 113 deletions(-) diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index fd3491c7fe0..11a8bcb0772 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -1,15 +1,27 @@ diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 8e7430bf69e..61cf7c1b665 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -143,10 +143,10 @@ export default { pipelineActionRequestComplete() { if (this.type === 'PIPELINES_TABLE') { // warn the table to update - eventHub.$emit('clickedDropdown'); + eventHub.$emit('refreshPipelinesTable'); } else { - // refresh the content - this.fetchJobs(); + // close the dropdown in mr widget + $(this.$refs.dropdown).dropdown('toggle'); } }, }, @@ -167,6 +167,7 @@ export default { id="stageDropdown" aria-haspopup="true" aria-expanded="false" + ref="dropdown" > { expect(component.$el.querySelector('svg')).toBeDefined(); }); - it('renders a loading icon while component is loading', done => { - component.isLoading = true; - - component.$nextTick() - .then(() => { - expect(component.$el.querySelector('.fa-spin')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - describe('on click', () => { it('emits `pipelineActionRequestComplete` after a successfull request', done => { spyOn(component, '$emit'); component.$el.click(); - expect(component.isLoading).toEqual(true); component.$nextTick() .then(() => { diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js index 2ba5ecf92e7..16f6db39d6a 100644 --- a/spec/javascripts/pipelines/stage_spec.js +++ b/spec/javascripts/pipelines/stage_spec.js @@ -111,7 +111,7 @@ describe('Pipelines stage component', () => { }); describe('within pipeline table', () => { - it('emits `clickedDropdown` event when `pipelineActionRequestComplete` is triggered', done => { + it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', done => { spyOn(eventHub, '$emit'); component.type = 'PIPELINES_TABLE'; @@ -121,34 +121,12 @@ describe('Pipelines stage component', () => { component.$el.querySelector('.js-ci-action').click(); component.$nextTick() .then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown'); - - expect(eventHub.$emit).toHaveBeenCalledTimes(2); + expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable'); }) .then(done) .catch(done.fail); }, 0); }); }); - - describe('without a type', () => { - it('fetches dropdown content again', done => { - spyOn(component, 'fetchJobs').and.callThrough(); - - component.$el.querySelector('button').click(); - - expect(component.fetchJobs).toHaveBeenCalledTimes(1); - - setTimeout(() => { - component.$el.querySelector('.js-ci-action').click(); - component.$nextTick() - .then(() => { - expect(component.fetchJobs).toHaveBeenCalledTimes(2); - }) - .then(done) - .catch(done.fail); - }, 0); - }); - }); }); }); -- cgit v1.2.3 From 28c6f4f9f488ea6f2b1cb16104fa3d7c31caa81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Ka=CC=88mmerle?= Date: Wed, 16 May 2018 18:55:22 +0200 Subject: Update gitlab.pot with new externalized strings --- locale/gitlab.pot | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 90cdfd0dd03..14228b18332 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: 2018-05-15 15:05+0200\n" -"PO-Revision-Date: 2018-05-15 15:05+0200\n" +"POT-Creation-Date: 2018-05-16 18:52+0200\n" +"PO-Revision-Date: 2018-05-16 18:52+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -1872,6 +1872,9 @@ msgstr "" msgid "Enable the Performance Bar for a given group." msgstr "" +msgid "Ends at (UTC)" +msgstr "" + msgid "Environments" msgstr "" @@ -3780,6 +3783,9 @@ msgstr "" msgid "Started" msgstr "" +msgid "Starts at (UTC)" +msgstr "" + msgid "Status" msgstr "" -- cgit v1.2.3 From c75ae23729771869591b6bc4f1528581d3bfe999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 16 May 2018 20:20:11 +0200 Subject: Add :weight as an allowed serializable field in app/controllers/boards/issues_controller.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/boards/issues_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 7d7ff217e5d..09e143c23e8 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -94,7 +94,7 @@ module Boards def serialize_as_json(resource) resource.as_json( - only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], + only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight], labels: true, issue_endpoints: true, include_full_project_path: board.group_board?, -- cgit v1.2.3 From 6d81905fafb23f20520eb5ffa46c94b18ccc8686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 16 May 2018 20:26:22 +0200 Subject: Introduce a new Keys::DestroyService service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/profiles/keys_controller.rb | 2 +- app/services/keys/base_service.rb | 2 +- app/services/keys/destroy_service.rb | 12 ++++++++++++ spec/services/keys/destroy_service_spec.rb | 13 +++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 app/services/keys/destroy_service.rb create mode 100644 spec/services/keys/destroy_service_spec.rb diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index f0e5d2aa94e..12a6cd11f80 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -23,7 +23,7 @@ class Profiles::KeysController < Profiles::ApplicationController def destroy @key = current_user.keys.find(params[:id]) - @key.destroy + Keys::DestroyService.new(current_user).execute(@key) respond_to do |format| format.html { redirect_to profile_keys_url, status: 302 } diff --git a/app/services/keys/base_service.rb b/app/services/keys/base_service.rb index f78791932a7..df8e82f5f60 100644 --- a/app/services/keys/base_service.rb +++ b/app/services/keys/base_service.rb @@ -2,7 +2,7 @@ module Keys class BaseService attr_accessor :user, :params - def initialize(user, params) + def initialize(user, params = {}) @user, @params = user, params @ip_address = @params.delete(:ip_address) end diff --git a/app/services/keys/destroy_service.rb b/app/services/keys/destroy_service.rb new file mode 100644 index 00000000000..785cfa3a1d8 --- /dev/null +++ b/app/services/keys/destroy_service.rb @@ -0,0 +1,12 @@ +module Keys + class DestroyService < ::Keys::BaseService + def execute(key) + key.destroy if destroy_possible?(key) + end + + # overriden in EE::Keys::DestroyService + def destroy_possible?(key) + true + end + end +end diff --git a/spec/services/keys/destroy_service_spec.rb b/spec/services/keys/destroy_service_spec.rb new file mode 100644 index 00000000000..28ac72ddd42 --- /dev/null +++ b/spec/services/keys/destroy_service_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Keys::DestroyService do + let(:user) { create(:user) } + + subject { described_class.new(user) } + + it 'destroys a key' do + key = create(:key) + + expect { subject.execute(key) }.to change(Key, :count).by(-1) + end +end -- cgit v1.2.3 From 8d024ba79a2e2a2f2d34f4ee678b496f0fbe64f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 16 May 2018 20:39:29 +0200 Subject: Backport changes from EE to minimize the CE/EE diff in Projects::Settings::IntegrationsController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/projects/settings/integrations_controller.rb | 9 ++++++++- app/models/project.rb | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb index 1ff08cce8cb..d9fecfecc40 100644 --- a/app/controllers/projects/settings/integrations_controller.rb +++ b/app/controllers/projects/settings/integrations_controller.rb @@ -11,7 +11,14 @@ module Projects @hook = ProjectHook.new # Services - @services = @project.find_or_initialize_services + @services = @project.find_or_initialize_services(exceptions: service_exceptions) + end + + private + + # Returns a list of services that should be hidden from the list + def service_exceptions + @project.disabled_services.dup end end end diff --git a/app/models/project.rb b/app/models/project.rb index 534a0e630af..107ee5f9a7e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -998,7 +998,7 @@ class Project < ActiveRecord::Base available_services_names = Service.available_services_names - exceptions - available_services_names.map do |service_name| + available_services = available_services_names.map do |service_name| service = find_service(services, service_name) if service @@ -1015,6 +1015,14 @@ class Project < ActiveRecord::Base end end end + + available_services.reject do |service| + disabled_services.include?(service.to_param) + end + end + + def disabled_services + [] end def find_or_initialize_service(name) -- cgit v1.2.3 From 42ab6f8557505595f86604735d8805f879247da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 16 May 2018 21:23:43 +0200 Subject: Move attribute casting to #cached_attribute --- app/models/concerns/redis_cacheable.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index 4fdaaddeee7..d40df5ba2d4 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -8,16 +8,15 @@ module RedisCacheable def cached_attr_reader(*attributes) attributes.each do |attribute| define_method(attribute) do - cached_value = cached_attribute(attribute) - cached_value = cast_value_from_cache(attribute, cached_value) if cached_value - cached_value || read_attribute(attribute) + cached_attribute(attribute) || read_attribute(attribute) end end end end def cached_attribute(attribute) - (cached_attributes || {})[attribute] + cached_value = (cached_attributes || {})[attribute] + cast_value_from_cache(attribute, cached_value) if cached_value end def cache_attributes(values) -- cgit v1.2.3 From a4b0876b391f0717365cabd78cf9715b64649797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 16 May 2018 21:36:20 +0200 Subject: Add attribute check in cached getter --- app/models/concerns/redis_cacheable.rb | 2 ++ spec/models/concerns/redis_cacheable_spec.rb | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index d40df5ba2d4..a8d96f63d7a 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -8,6 +8,8 @@ module RedisCacheable def cached_attr_reader(*attributes) attributes.each do |attribute| define_method(attribute) do + raise ArgumentError, "Not a database attribute" unless self.has_attribute?(attribute) + cached_attribute(attribute) || read_attribute(attribute) end end diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 827629c3180..2da0f33e27b 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -10,6 +10,10 @@ describe RedisCacheable do def cast_value_from_cache(attribute, cached_value) cached_value end + + def has_attribute?(attribute) + attributes.has_key?(attribute) + end end end -- cgit v1.2.3 From 4e1bb1d1014237df79db6b3cc2beb24228a4b228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 16 May 2018 21:49:09 +0200 Subject: Move argument check to cached getter definition class method --- app/models/concerns/redis_cacheable.rb | 4 ++-- spec/models/concerns/redis_cacheable_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index a8d96f63d7a..bf046c0e333 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -7,9 +7,9 @@ module RedisCacheable class_methods do def cached_attr_reader(*attributes) attributes.each do |attribute| - define_method(attribute) do - raise ArgumentError, "Not a database attribute" unless self.has_attribute?(attribute) + raise ArgumentError, "Not a database attribute" unless self.attribute_names.include?(attribute.to_s) + define_method(attribute) do cached_attribute(attribute) || read_attribute(attribute) end end diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 2da0f33e27b..089ae080b0c 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -11,8 +11,8 @@ describe RedisCacheable do cached_value end - def has_attribute?(attribute) - attributes.has_key?(attribute) + def self.attribute_names + %w[name time] end end end -- cgit v1.2.3 From eeb955a66d2410d7117737ecd1b33b0cfe67327b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 16 May 2018 22:43:38 +0200 Subject: Revert "Move argument check to cached getter definition class method" This reverts commit 4e1bb1d1014237df79db6b3cc2beb24228a4b228. --- app/models/concerns/redis_cacheable.rb | 4 ++-- spec/models/concerns/redis_cacheable_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index bf046c0e333..a8d96f63d7a 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -7,9 +7,9 @@ module RedisCacheable class_methods do def cached_attr_reader(*attributes) attributes.each do |attribute| - raise ArgumentError, "Not a database attribute" unless self.attribute_names.include?(attribute.to_s) - define_method(attribute) do + raise ArgumentError, "Not a database attribute" unless self.has_attribute?(attribute) + cached_attribute(attribute) || read_attribute(attribute) end end diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 089ae080b0c..2da0f33e27b 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -11,8 +11,8 @@ describe RedisCacheable do cached_value end - def self.attribute_names - %w[name time] + def has_attribute?(attribute) + attributes.has_key?(attribute) end end end -- cgit v1.2.3 From f494f2711a5a19b751c50832d8ae15c01ce3c3ee Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Wed, 16 May 2018 15:58:20 -0500 Subject: Respect the inheritance chain between Ci::Build and CommitStatus Also moves the assertions were they belong --- app/presenters/ci/build_presenter.rb | 25 +------------------------ app/presenters/commit_status_presenter.rb | 24 +++++++++++++++++++++++- spec/models/commit_status_spec.rb | 7 ++----- spec/models/generic_commit_status_spec.rb | 6 ++++++ spec/presenters/ci/build_presenter_spec.rb | 2 +- spec/presenters/commit_status_presenter_spec.rb | 15 +++++++++++++++ 6 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 spec/presenters/commit_status_presenter_spec.rb diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index 4873d7ce662..e0aaa5cb736 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -1,16 +1,5 @@ module Ci - class BuildPresenter < Gitlab::View::Presenter::Delegated - CALLOUT_FAILURE_MESSAGES = { - unknown_failure: 'There is an unknown failure, please try again', - script_failure: 'There has been a script failure. Check the job log for more information', - api_failure: 'There has been an API failure, please try again', - stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again', - runner_system_failure: 'There has been a runner system failure, please try again', - missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information' - }.freeze - - presents :build - + class BuildPresenter < CommitStatusPresenter def erased_by_user? # Build can be erased through API, therefore it does not have # `erased_by` user assigned in that case. @@ -44,14 +33,6 @@ module Ci "#{subject.name} - #{detailed_status.status_tooltip}" end - def callout_failure_message - CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym] - end - - def recoverable? - failed? && !unrecoverable? - end - private def tooltip_for_badge @@ -61,9 +42,5 @@ module Ci def detailed_status @detailed_status ||= subject.detailed_status(user) end - - def unrecoverable? - script_failure? || missing_dependency_failure? - end end end diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb index 028deaf235c..c7f7aa836bd 100644 --- a/app/presenters/commit_status_presenter.rb +++ b/app/presenters/commit_status_presenter.rb @@ -1,2 +1,24 @@ -class CommitStatusPresenter < Ci::BuildPresenter +class CommitStatusPresenter < Gitlab::View::Presenter::Delegated + CALLOUT_FAILURE_MESSAGES = { + unknown_failure: 'There is an unknown failure, please try again', + script_failure: 'There has been a script failure. Check the job log for more information', + api_failure: 'There has been an API failure, please try again', + stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again', + runner_system_failure: 'There has been a runner system failure, please try again', + missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information' + }.freeze + + presents :build + + def callout_failure_message + CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym] + end + + def recoverable? + failed? && !unrecoverable? + end + + def unrecoverable? + script_failure? || missing_dependency_failure? + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index eddcf616b53..f3f2bc28d2c 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -567,11 +567,8 @@ describe CommitStatus do end describe '#present' do - let(:generic_commit_status) { create(:generic_commit_status) } + subject { commit_status.present } - it 'returns a presenter' do - expect(commit_status.present).to be_a(Ci::BuildPresenter) - expect(generic_commit_status.present).to be_a(Ci::BuildPresenter) - end + it { is_expected.to be_a(CommitStatusPresenter) } end end diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 673049d1cc4..a3e68d2e646 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -78,4 +78,10 @@ describe GenericCommitStatus do it { is_expected.not_to be_nil } end end + + describe '#present' do + subject { generic_commit_status.present } + + it { is_expected.to be_a(GenericCommitStatusPresenter) } + end end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 4bc005df2fc..efd175247b5 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -10,7 +10,7 @@ describe Ci::BuildPresenter do end it 'inherits from Gitlab::View::Presenter::Delegated' do - expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + expect(described_class.ancestors).to include(Gitlab::View::Presenter::Delegated) end describe '#initialize' do diff --git a/spec/presenters/commit_status_presenter_spec.rb b/spec/presenters/commit_status_presenter_spec.rb new file mode 100644 index 00000000000..f81ee44e371 --- /dev/null +++ b/spec/presenters/commit_status_presenter_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe CommitStatusPresenter do + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + + subject(:presenter) do + described_class.new(build) + end + + it 'inherits from Gitlab::View::Presenter::Delegated' do + expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + end +end -- cgit v1.2.3 From 51f5ee33c448ee8a157ad12dac80482a11a7c72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 17 May 2018 01:12:37 +0200 Subject: Use Gitlab.rails5? for checking if on rails5 --- app/models/concerns/redis_cacheable.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index a8d96f63d7a..3d24f0dfdc1 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -45,10 +45,10 @@ module RedisCacheable end def cast_value_from_cache(attribute, value) - if self.class.column_for_attribute(attribute).respond_to?(:type_cast_from_database) - self.class.column_for_attribute(attribute).type_cast_from_database(value) - else + if Gitlab.rails5? self.class.type_for_attribute(attribute).cast(value) + else + self.class.column_for_attribute(attribute).type_cast_from_database(value) end end end -- cgit v1.2.3 From 94b209b32c4d38eb7a350c175aff551f789b7c6b Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Wed, 16 May 2018 16:17:55 +0200 Subject: Move git archives downloading to Gitaly --- changelogs/unreleased/zj-workhorse-archive-mandatory.yml | 5 +++++ lib/gitlab/git/repository.rb | 4 ++-- lib/gitlab/workhorse.rb | 7 +------ spec/lib/gitlab/git/repository_spec.rb | 4 ---- spec/lib/gitlab/workhorse_spec.rb | 12 +----------- 5 files changed, 9 insertions(+), 23 deletions(-) create mode 100644 changelogs/unreleased/zj-workhorse-archive-mandatory.yml diff --git a/changelogs/unreleased/zj-workhorse-archive-mandatory.yml b/changelogs/unreleased/zj-workhorse-archive-mandatory.yml new file mode 100644 index 00000000000..3a4a351a2b9 --- /dev/null +++ b/changelogs/unreleased/zj-workhorse-archive-mandatory.yml @@ -0,0 +1,5 @@ +--- +title: Workhorse will use Gitaly to create archives +merge_request: +author: +type: other diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 061865a7acf..c7a755319dc 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -403,10 +403,10 @@ module Gitlab prefix = archive_prefix(ref, commit.id, append_sha: append_sha) { - 'RepoPath' => path, 'ArchivePrefix' => prefix, 'ArchivePath' => archive_file_path(storage_path, commit.id, prefix, format), - 'CommitId' => commit.id + 'CommitId' => commit.id, + 'GitalyRepository' => gitaly_repository.to_h } end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 1f060de657d..e893e46ee86 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -65,12 +65,7 @@ module Gitlab params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha) raise "Repository or ref not found" if params.empty? - if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) - params.merge!( - 'GitalyServer' => gitaly_server_hash(repository), - 'GitalyRepository' => repository.gitaly_repository.to_h - ) - end + params['GitalyServer'] = gitaly_server_hash(repository) # If present DisableCache must be a Boolean. Otherwise workhorse ignores it. params['DisableCache'] = true if git_archive_cache_disabled? diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index fcb690d8aa3..740d12c443d 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -249,10 +249,6 @@ describe Gitlab::Git::Repository, seed_helper: true do subject(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: append_sha) } - it 'sets RepoPath to the repository path' do - expect(metadata['RepoPath']).to eq(repository.path) - end - it 'sets CommitId to the commit SHA' do expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID) end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index e732b089d44..660671cefaf 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Workhorse do - let(:project) { create(:project, :repository) } + set(:project) { create(:project, :repository) } let(:repository) { project.repository } def decode_workhorse_header(array) @@ -55,16 +55,6 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_archive feature is disabled', :disable_gitaly do - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) - - expect(key).to eq('Gitlab-Workhorse-Send-Data') - expect(command).to eq('git-archive') - expect(params).to eq(base_params) - end - end - context "when the repository doesn't have an archive file path" do before do allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) -- cgit v1.2.3 From 92b0cefb95408d7ae78eaaa8deaf26101b3b70bb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 17 May 2018 08:47:00 +0100 Subject: fixed up spacing above & below lists fixed `no changes` spacing --- app/assets/stylesheets/pages/repo.scss | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index baef3a64be2..e52fa7e9b33 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -41,6 +41,7 @@ flex: 1; padding-left: $gl-padding; padding-right: $gl-padding; + padding-bottom: $grid-size; .file { cursor: pointer; @@ -523,7 +524,7 @@ .multi-file-commit-list { flex: 1; overflow: auto; - padding: $gl-padding 0; + padding: $grid-size 0; margin-left: -$grid-size; margin-right: -$grid-size; min-height: 60px; @@ -532,6 +533,11 @@ margin-left: 0; margin-right: 0; } + + &.help-block { + margin-left: 0; + right: 0; + } } .multi-file-commit-list-item { @@ -994,6 +1000,7 @@ .ide-tree-header { display: flex; align-items: center; + margin-bottom: 8px; padding: 12px 0; border-bottom: 1px solid $white-dark; -- cgit v1.2.3 From b1135b61e60654f5b3f1b68e18c40627b5fce786 Mon Sep 17 00:00:00 2001 From: "Balasankar \"Balu\" C" Date: Thu, 3 May 2018 12:37:28 +0530 Subject: Add doc about backup-restore in docker and k8s --- doc/raketasks/backup_restore.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 785cc32d590..77139c50d07 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -64,6 +64,13 @@ If you are running GitLab within a Docker container, you can run the backup from docker exec -t gitlab-rake gitlab:backup:create ``` +If you are using the gitlab-omnibus helm chart on a Kubernetes cluster, you can +run the backup task on the gitlab application pod using kubectl + +``` +kubectl exec -it gitlab-rake gitlab:backup:create +``` + Example output: ``` @@ -601,6 +608,34 @@ If there is a GitLab version mismatch between your backup tar file and the insta version of GitLab, the restore command will abort with an error. Install the [correct GitLab version](https://packages.gitlab.com/gitlab/) and try again. +### Restore for Docker image and gitlab-omnibus helm chart + +For GitLab installations using docker image or the gitlab-omnibus helm chart on +a Kubernetes cluster, restore task expects the restore directories to be empty. +However, with docker and Kubernetes volume mounts, some system level directories +may be created at the volume roots, like `lost+found` directory found in Linux +operating systems. These directories are usually owned by `root`, which can +cause access permission errors since the restore rake task runs as `git` user. +So, to restore a GitLab installation, users have to confirm the restore target +directories are empty. + +For both these installation types, the backup tarball has to be available in the +backup location (default location is `/var/opt/gitlab/backups`). + +For docker installations, the restore task can be run from host using the +command + +``` +docker exec -it gitlab-rake gitlab:backup:restore +``` + +Similarly, for gitlab-omnibus helm chart, the restore task can be run on the +gitlab application pod using kubectl + +``` +kubectl exec -it gitlab-rake gitlab:backup:restore +``` + ## Alternative backup strategies If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow. -- cgit v1.2.3 From 068186555cb85e85bbfe04afe858fb3eb4801207 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 17 May 2018 09:45:43 +0100 Subject: Update method name --- app/assets/javascripts/environments/mixins/environments_mixin.js | 6 +++--- .../javascripts/environments/services/environments_service.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index c8745e35802..a7a79dbca70 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -66,7 +66,7 @@ export default { updateContent(parameters) { this.updateInternalState(parameters); // fetch new data - return this.service.get(this.requestData) + return this.service.fetchEnvironments(this.requestData) .then(response => this.successCallback(response)) .then(() => { // restart polling @@ -101,7 +101,7 @@ export default { fetchEnvironments() { this.isLoading = true; - return this.service.get(this.requestData) + return this.service.fetchEnvironments(this.requestData) .then(this.successCallback) .catch(this.errorCallback); }, @@ -137,7 +137,7 @@ export default { this.poll = new Poll({ resource: this.service, - method: 'get', + method: 'fetchEnvironments', data: this.requestData, successCallback: this.successCallback, errorCallback: this.errorCallback, diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js index 22563fad8d2..3b121551aca 100644 --- a/app/assets/javascripts/environments/services/environments_service.js +++ b/app/assets/javascripts/environments/services/environments_service.js @@ -6,7 +6,7 @@ export default class EnvironmentsService { this.folderResults = 3; } - get(options = {}) { + fetchEnvironments(options = {}) { const { scope, page } = options; return axios.get(this.environmentsEndpoint, { params: { scope, page } }); } -- cgit v1.2.3 From 1bf74bfd255372f17f9135ba0ce6ad9111c845d9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 17 May 2018 10:25:11 +0100 Subject: Moves string to a constant --- app/assets/javascripts/pipelines/components/pipelines_table_row.vue | 4 +++- app/assets/javascripts/pipelines/components/stage.vue | 3 ++- app/assets/javascripts/pipelines/constants.js | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 41b5e418dd1..fdf8c04207f 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -9,6 +9,7 @@ import CommitComponent from '../../vue_shared/components/commit.vue'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; import Icon from '../../vue_shared/components/icon.vue'; + import { PIPELINES_TABLE } from '../constants.js' /** * Pipeline table row. @@ -46,6 +47,7 @@ required: true, }, }, + pipelinesTable: PIPELINES_TABLE, data() { return { isRetrying: false, @@ -297,7 +299,7 @@ v-for="(stage, index) in pipeline.details.stages" :key="index"> diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 61cf7c1b665..1e6392cf60a 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -21,6 +21,7 @@ import Icon from '../../vue_shared/components/icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import JobComponent from './graph/job_component.vue'; import tooltip from '../../vue_shared/directives/tooltip'; +import { PIPELINES_TABLE } from '../constants.js' export default { components: { @@ -141,7 +142,7 @@ export default { }, pipelineActionRequestComplete() { - if (this.type === 'PIPELINES_TABLE') { + if (this.type === PIPELINES_TABLE) { // warn the table to update eventHub.$emit('refreshPipelinesTable'); } else { diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js index b384c7500e7..eaa11a84cb9 100644 --- a/app/assets/javascripts/pipelines/constants.js +++ b/app/assets/javascripts/pipelines/constants.js @@ -1,2 +1,2 @@ -// eslint-disable-next-line import/prefer-default-export export const CANCEL_REQUEST = 'CANCEL_REQUEST'; +export const PIPELINES_TABLE = 'PIPELINES_TABLE'; -- cgit v1.2.3 From a63ada5e77c4d817b05552d066dc6004003aaf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 17 May 2018 11:57:23 +0200 Subject: Include class name and argument name in argument error --- app/models/concerns/redis_cacheable.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index 3d24f0dfdc1..b5425295130 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -8,7 +8,9 @@ module RedisCacheable def cached_attr_reader(*attributes) attributes.each do |attribute| define_method(attribute) do - raise ArgumentError, "Not a database attribute" unless self.has_attribute?(attribute) + unless self.has_attribute?(attribute) + raise ArgumentError, "`cached_attr_reader` requires the #{self.class.name}\##{attribute} attribute to have a database column" + end cached_attribute(attribute) || read_attribute(attribute) end -- cgit v1.2.3 From 0ce63efe966840edb6e6184cf1abcef272a24dfc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 17 May 2018 12:29:47 +0200 Subject: Add extended /regexp/ scheme support to untrusted regexp --- lib/gitlab/untrusted_regexp.rb | 23 +++++++++++++++ spec/lib/gitlab/untrusted_regexp_spec.rb | 49 +++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb index ce1cf737663..70d1a7c6535 100644 --- a/lib/gitlab/untrusted_regexp.rb +++ b/lib/gitlab/untrusted_regexp.rb @@ -9,6 +9,8 @@ module Gitlab # there is a strict limit on total execution time. See the RE2 documentation # at https://github.com/google/re2/wiki/Syntax for more details. class UntrustedRegexp + require_dependency 're2' + delegate :===, :source, to: :regexp def initialize(pattern, multiline: false) @@ -52,6 +54,27 @@ module Gitlab Regexp.new(pattern) end + def self.valid?(pattern) + self.fabricate(pattern) + rescue RegexpError + false + end + + def self.fabricate(pattern) + matches = pattern.match(%r{^/(?.+)/(?[ismU]*)$}) + + if matches + expression = matches[:regexp] + flags = matches[:flags] + + expression.prepend("(?#{flags})") if flags.present? + + self.new(expression, multiline: false) + else + self.new(pattern, multiline: false) + end + end + private attr_reader :regexp diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb index 0ee7fa1e570..4bca320ac2c 100644 --- a/spec/lib/gitlab/untrusted_regexp_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -1,6 +1,53 @@ -require 'spec_helper' +require 'fast_spec_helper' +require 'support/shared_examples/malicious_regexp_shared_examples' describe Gitlab::UntrustedRegexp do + describe '.valid?' do + it 'returns true if regexp is valid' do + end + + it 'returns true if regexp is invalid' do + end + end + + describe '.fabricate' do + context 'when regexp is using /regexp/ scheme with flags' do + it 'fabricates regexp with a single flag' do + regexp = described_class.fabricate('/something/i') + + expect(regexp).to eq described_class.new('(?i)something') + expect(regexp.scan('SOMETHING')).to be_one + end + + it 'fabricates regexp with multiple flags' do + regexp = described_class.fabricate('/something/im') + + expect(regexp).to eq described_class.new('(?im)something') + end + + it 'fabricates regexp without flags' do + regexp = described_class.fabricate('/something/') + + expect(regexp).to eq described_class.new('something') + end + end + + context 'when regexp is not plain pattern' do + it 'fabricates regexp without flags' do + regexp = described_class.fabricate('something') + + expect(regexp).to eq described_class.new('something') + end + end + + context 'when regexp is invalid' do + it 'raises an error' do + expect { described_class.fabricate('/some ( thing/') } + .to raise_error(RegexpError) + end + end + end + describe '#initialize' do subject { described_class.new(pattern) } -- cgit v1.2.3 From a1f1e08670a7f8bd5499e16c778be16106210a44 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 17 May 2018 12:35:20 +0200 Subject: Add anti-corruption layer above expressions pattern matching --- .../ci/pipeline/expression/lexeme/pattern.rb | 4 ++-- .../ci/pipeline/expression/lexeme/pattern_spec.rb | 28 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index 62927441035..53fb5f769d8 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -6,14 +6,14 @@ module Gitlab require_dependency 're2' class Pattern < Lexeme::Value - PATTERN = %r{/(?.+)/}.freeze + PATTERN = %r{^(?/.+/[ismU]*)$}.freeze def initialize(regexp) @value = regexp end def evaluate(variables = {}) - Gitlab::UntrustedRegexp.new(@value) + Gitlab::UntrustedRegexp.fabricate(@value) rescue RegexpError raise Expression::RuntimeError, 'Invalid regular expression!' end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index a14a28056d8..6435ee5c915 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -42,6 +42,34 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do expect(token).to be_nil end + + it 'support single flag' do + scanner = StringScanner.new('/pattern/i') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('(?i)pattern') + end + + it 'support multiple flags' do + scanner = StringScanner.new('/pattern/im') + + token = described_class.scan(scanner) + + expect(token).not_to be_nil + expect(token.build.evaluate) + .to eq Gitlab::UntrustedRegexp.new('(?im)pattern') + end + + it 'does not support arbitrary flags' do + scanner = StringScanner.new('/pattern/x') + + token = described_class.scan(scanner) + + expect(token).to be_nil + end end describe '#evaluate' do -- cgit v1.2.3 From 9e61d26c35acd6e60ac0fb0df711dbfdfda7c448 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 17 May 2018 10:25:11 +0100 Subject: Fix eslint --- app/assets/javascripts/pipelines/components/pipelines_table_row.vue | 2 +- app/assets/javascripts/pipelines/components/stage.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index fdf8c04207f..0f671ceea21 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -9,7 +9,7 @@ import CommitComponent from '../../vue_shared/components/commit.vue'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; import Icon from '../../vue_shared/components/icon.vue'; - import { PIPELINES_TABLE } from '../constants.js' + import { PIPELINES_TABLE } from '../constants'; /** * Pipeline table row. diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 1e6392cf60a..f9769815796 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -21,7 +21,7 @@ import Icon from '../../vue_shared/components/icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import JobComponent from './graph/job_component.vue'; import tooltip from '../../vue_shared/directives/tooltip'; -import { PIPELINES_TABLE } from '../constants.js' +import { PIPELINES_TABLE } from '../constants'; export default { components: { -- cgit v1.2.3 From 8b3e21b66b734b38e88f63727ee77b978ea21bfc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 17 May 2018 12:44:46 +0200 Subject: Add variables expression pattern validation support --- lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb | 4 ++++ spec/lib/gitlab/ci/config/entry/policy_spec.rb | 10 +++++++++- spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index 53fb5f769d8..70a221010f3 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -10,6 +10,10 @@ module Gitlab def initialize(regexp) @value = regexp + + unless Gitlab::UntrustedRegexp.valid?(@value) + raise Lexer::SyntaxError, 'Invalid regular expression!' + end end def evaluate(variables = {}) diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 08718c382b9..83d39b82068 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -111,7 +111,15 @@ describe Gitlab::Ci::Config::Entry::Policy do context 'when specifying invalid variables expressions token' do let(:config) { { variables: ['$MY_VAR == 123'] } } - it 'reports an error about invalid statement' do + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when using invalid variables expressions regexp' do + let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } + + it 'reports an error about invalid expression' do expect(entry.errors).to include /invalid expression syntax/ end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index 6435ee5c915..c63c38b1dbc 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -6,6 +6,11 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do expect(described_class.build('/.*/')) .to be_a(described_class) end + + it 'raises an error if pattern is invalid' do + expect { described_class.build('/ some ( thin/i') } + .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError) + end end describe '.type' do @@ -80,6 +85,8 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do end it 'raises error if evaluated regexp is not valid' do + allow(Gitlab::UntrustedRegexp).to receive(:valid?).and_return(true) + regexp = described_class.new('invalid ( .*') expect { regexp.evaluate } -- cgit v1.2.3 From c808de25515f9c977270a0663796ce34d1c81fd1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 17 May 2018 12:47:41 +0200 Subject: Update variables expressions pattern matching docs --- doc/ci/variables/README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 58acc030aee..f66b2b374ba 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -537,11 +537,8 @@ Below you can find supported syntax reference: It is possible perform pattern matching against a variable and regular expression. Expression like this evaluates to truth if matches are found. - Pattern matching is case-sensitive by default. Prepend `(?i)` to a - match-group to make a pattern case-insensitive. - - Under the hood we are using [RE2 library][re2-library], see - [syntax documentation][re2-syntax] for reference. + Pattern matching is case-sensitive by default. Use `i` flag modifier, like + `/pattern/i` to make a pattern case-insensitive. ### Unsupported predefined variables @@ -583,5 +580,3 @@ These variables are also not supported in a context of a [builds-policies]: ../yaml/README.md#only-and-except-complex [dynamic-environments]: ../environments.md#dynamic-environments [gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token -[re2-library]: https://github.com/google/re2 -[re2-syntax]: https://github.com/google/re2/wiki/Syntax -- cgit v1.2.3 From 61d55b56ab4ee7d11484eeea6fabb2412af6578f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 17 May 2018 12:55:19 +0200 Subject: Update variables expressions statement specs --- spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 1ceb373e19c..11e73294f18 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -100,7 +100,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do "$PRESENT_VARIABLE =~ /^var.*/" | false "$EMPTY_VARIABLE =~ /var.*/" | false "$UNDEFINED_VARIABLE =~ /var.*/" | false - "$PRESENT_VARIABLE =~ /(?i)VAR.*/" | true + "$PRESENT_VARIABLE =~ /VAR.*/i" | true end with_them do -- cgit v1.2.3 From 13298bb3d1f1d8f10be5568d1617348e6ec0c0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 17 May 2018 13:06:23 +0200 Subject: Add test for #cast_value_from_cache --- spec/models/concerns/redis_cacheable_spec.rb | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 2da0f33e27b..066021a32cd 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -83,4 +83,40 @@ describe RedisCacheable do expect(instance.name).to eq('new_value') end end + + describe '#cast_value_from_cache' do + let(:instance) { Ci::Runner.new } + + subject { instance.__send__(:cast_value_from_cache, attribute, value) } + + shared_context 'runner contacted_at context' do + let(:attribute) { :contacted_at } + let(:value) { '2018-05-07 13:53:08 UTC' } + end + + context 'on rails5' do + include_context 'runner contacted_at context' + + before do + allow(Gitlab).to receive(:rails5?).and_return(true) + end + + it 'converts cache string to appropriate type' do + pending 'Migration to rails5' + expect(subject).to be_an_instance_of(ActiveSupport::TimeWithZone) + end + end + + context 'not on rails5' do + include_context 'runner contacted_at context' + + before do + allow(Gitlab).to receive(:rails5?).and_return(false) + end + + it 'converts cache string to appropriate type' do + expect(subject).to be_an_instance_of(ActiveSupport::TimeWithZone) + end + end + end end -- cgit v1.2.3 From 17c4e53e80af6c652f427733dc7dce4b7f88c8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 17 May 2018 13:23:57 +0200 Subject: Remove rails5 specific tests for #cast_value_from_cache --- spec/models/concerns/redis_cacheable_spec.rb | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 066021a32cd..23c6c6233e9 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -85,34 +85,12 @@ describe RedisCacheable do end describe '#cast_value_from_cache' do - let(:instance) { Ci::Runner.new } - subject { instance.__send__(:cast_value_from_cache, attribute, value) } - shared_context 'runner contacted_at context' do + context 'with runner contacted_at' do + let(:instance) { Ci::Runner.new } let(:attribute) { :contacted_at } let(:value) { '2018-05-07 13:53:08 UTC' } - end - - context 'on rails5' do - include_context 'runner contacted_at context' - - before do - allow(Gitlab).to receive(:rails5?).and_return(true) - end - - it 'converts cache string to appropriate type' do - pending 'Migration to rails5' - expect(subject).to be_an_instance_of(ActiveSupport::TimeWithZone) - end - end - - context 'not on rails5' do - include_context 'runner contacted_at context' - - before do - allow(Gitlab).to receive(:rails5?).and_return(false) - end it 'converts cache string to appropriate type' do expect(subject).to be_an_instance_of(ActiveSupport::TimeWithZone) -- cgit v1.2.3 From 70985aa19b389c2ee8234edfbb516b5403a7bfcf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 17 Apr 2018 15:13:38 +0200 Subject: Limit the number of pipelines to count When displaying the project pipelines dashboard we display a few tabs for different pipeline states. For every such tab we count the number of pipelines that belong to it. For large projects such as GitLab CE this means having to count over 80 000 rows, which can easily take between 70 and 100 milliseconds per query. To improve this we apply a technique we already use for search results: we limit the number of rows to count. The current limit is 1000, which means that if more than 1000 rows are present for a state we will show "1000+" instead of the exact number. The SQL queries used for this perform much better than a regular COUNT, even when a project has a lot of pipelines. Prior to these changes we would end up running a query like this: SELECT COUNT(*) FROM ci_pipelines WHERE project_id = 13083 AND status IN ('success', 'failed', 'canceled') This would produce a plan along the lines of the following: Aggregate (cost=3147.55..3147.56 rows=1 width=8) (actual time=501.413..501.413 rows=1 loops=1) Buffers: shared hit=17116 read=861 dirtied=2 -> Index Only Scan using index_ci_pipelines_on_project_id_and_ref_and_status_and_id on ci_pipelines (cost=0.56..2984.14 rows=65364 width=0) (actual time=0.095..490.263 rows=80388 loops=1) Index Cond: (project_id = 13083) Filter: ((status)::text = ANY ('{success,failed,canceled}'::text[])) Rows Removed by Filter: 2894 Heap Fetches: 353 Buffers: shared hit=17116 read=861 dirtied=2 Planning time: 1.409 ms Execution time: 501.519 ms Using the LIMIT count technique we instead run the following query: SELECT COUNT(*) FROM ( SELECT 1 FROM ci_pipelines WHERE project_id = 13083 AND status IN ('success', 'failed', 'canceled') LIMIT 1001 ) for_count This query produces the following plan: Aggregate (cost=58.77..58.78 rows=1 width=8) (actual time=1.726..1.727 rows=1 loops=1) Buffers: shared hit=169 read=15 -> Limit (cost=0.56..46.25 rows=1001 width=4) (actual time=0.164..1.570 rows=1001 loops=1) Buffers: shared hit=169 read=15 -> Index Only Scan using index_ci_pipelines_on_project_id_and_ref_and_status_and_id on ci_pipelines (cost=0.56..2984.14 rows=65364 width=4) (actual time=0.162..1.426 rows=1001 loops=1) Index Cond: (project_id = 13083) Filter: ((status)::text = ANY ('{success,failed,canceled}'::text[])) Rows Removed by Filter: 9 Heap Fetches: 10 Buffers: shared hit=169 read=15 Planning time: 1.832 ms Execution time: 1.821 ms While this query still uses a Filter for the "status" field the number of rows that it may end up filtering (at most 1001) is small enough that an additional index does not appear to be necessary at this time. See https://gitlab.com/gitlab-org/gitlab-ce/issues/43132#note_68659234 for more information. --- app/controllers/projects/pipelines_controller.rb | 21 ++++++++++----------- .../projects/pipelines_controller_spec.rb | 8 ++++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index f7417a6a5aa..329aaafb76d 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -18,17 +18,10 @@ class Projects::PipelinesController < Projects::ApplicationController .page(params[:page]) .per(30) - @running_count = PipelinesFinder - .new(project, scope: 'running').execute.count - - @pending_count = PipelinesFinder - .new(project, scope: 'pending').execute.count - - @finished_count = PipelinesFinder - .new(project, scope: 'finished').execute.count - - @pipelines_count = PipelinesFinder - .new(project).execute.count + @running_count = limited_pipelines_count(project, 'running') + @pending_count = limited_pipelines_count(project, 'pending') + @finished_count = limited_pipelines_count(project, 'finished') + @pipelines_count = limited_pipelines_count(project) @pipelines.map(&:commit) # List commits for batch loading @@ -185,4 +178,10 @@ class Projects::PipelinesController < Projects::ApplicationController def authorize_update_pipeline! return access_denied! unless can?(current_user, :update_pipeline, @pipeline) end + + def limited_pipelines_count(project, scope = nil) + finder = PipelinesFinder.new(project, scope: scope) + + view_context.limited_counter_with_delimiter(finder.execute) + end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index a451bbb97b6..c22e455ea2d 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -35,10 +35,10 @@ describe Projects::PipelinesController do expect(json_response).to include('pipelines') expect(json_response['pipelines'].count).to eq 4 - expect(json_response['count']['all']).to eq 4 - expect(json_response['count']['running']).to eq 1 - expect(json_response['count']['pending']).to eq 1 - expect(json_response['count']['finished']).to eq 1 + expect(json_response['count']['all']).to eq '4' + expect(json_response['count']['running']).to eq '1' + expect(json_response['count']['pending']).to eq '1' + expect(json_response['count']['finished']).to eq '1' end context 'when performing gitaly calls', :request_store do -- cgit v1.2.3 From 19428e800895ba20eacb3357285acef8d69f6d8c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 7 May 2018 18:22:07 +0200 Subject: Preload pipeline data for project pipelines When displaying the pipelines of a project we now preload the following data: 1. Authors of the commits that belong to these pipelines 2. The number of warnings per pipeline, which is used by Ci::Pipeline#has_warnings? == Commit Authors Previously this data was queried for every Commit separately, leading to 20 SQL queries being executed in the worst case. With an average of 3 to 5 milliseconds per SQL query this could result in 100 milliseconds being spent in _just_ getting Commit authors. To preload this data Commit#author now uses BatchLoader (through Commit#lazy_author), and a separate module Gitlab::Ci::Pipeline::Preloader is used to ensure all authors are loaded before they are used. == Number of warnings This changes Ci::Pipeline#has_warnings? so it supports preloading of the number of warnings per pipeline. This removes the need for executing a COUNT(*) query for every pipeline just to see if it has any warnings or not. --- app/controllers/projects/pipelines_controller.rb | 2 +- app/models/ci/pipeline.rb | 13 +++- app/models/commit.rb | 28 +++++++- lib/gitlab/ci/pipeline/preloader.rb | 28 ++++++++ spec/lib/gitlab/ci/pipeline/preloader_spec.rb | 20 ++++++ spec/models/ci/pipeline_spec.rb | 27 ++++++++ spec/models/commit_spec.rb | 84 ++++++++++++++++++++++-- 7 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 lib/gitlab/ci/pipeline/preloader.rb create mode 100644 spec/lib/gitlab/ci/pipeline/preloader_spec.rb diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 329aaafb76d..91afe24b707 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -23,7 +23,7 @@ class Projects::PipelinesController < Projects::ApplicationController @finished_count = limited_pipelines_count(project, 'finished') @pipelines_count = limited_pipelines_count(project) - @pipelines.map(&:commit) # List commits for batch loading + Gitlab::Ci::Pipeline::Preloader.preload(@pipelines) respond_to do |format| format.html diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 1f49764e7cc..c26f0b6dcdc 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -406,7 +406,18 @@ module Ci end def has_warnings? - builds.latest.failed_but_allowed.any? + number_of_warnings.positive? + end + + def number_of_warnings + BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader| + Build.where(commit_id: pipeline_ids) + .latest + .failed_but_allowed + .group(:commit_id) + .count + .each { |id, amount| loader.call(id, amount) } + end end def set_config_source diff --git a/app/models/commit.rb b/app/models/commit.rb index b46f9f34689..56d4c86774e 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -224,8 +224,34 @@ class Commit Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) end + def lazy_author + BatchLoader.for(author_email.downcase).batch do |emails, loader| + # A Hash that maps user Emails to the corresponding User objects. The + # Emails at this point are the _primary_ Emails of the Users. + users_for_emails = User + .by_any_email(emails) + .each_with_object({}) { |user, hash| hash[user.email] = user } + + users_for_ids = users_for_emails + .values + .each_with_object({}) { |user, hash| hash[user.id] = user } + + # Some commits may have used an alternative Email address. In this case we + # need to query the "emails" table to map those addresses to User objects. + Email + .where(email: emails - users_for_emails.keys) + .pluck(:email, :user_id) + .each { |(email, id)| users_for_emails[email] = users_for_ids[id] } + + users_for_emails.each { |email, user| loader.call(email, user) } + end + end + def author - User.find_by_any_email(author_email.downcase) + # We use __sync so that we get the actual objects back (including an actual + # nil), instead of a wrapper, as returning a wrapped nil breaks a lot of + # code. + lazy_author.__sync end request_cache(:author) { author_email.downcase } diff --git a/lib/gitlab/ci/pipeline/preloader.rb b/lib/gitlab/ci/pipeline/preloader.rb new file mode 100644 index 00000000000..e7a2e5511cf --- /dev/null +++ b/lib/gitlab/ci/pipeline/preloader.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Pipeline + # Class for preloading data associated with pipelines such as commit + # authors. + module Preloader + def self.preload(pipelines) + # This ensures that all the pipeline commits are eager loaded before we + # start using them. + pipelines.each(&:commit) + + pipelines.each do |pipeline| + # This preloads the author of every commit. We're using "lazy_author" + # here since "author" immediately loads the data on the first call. + pipeline.commit.try(:lazy_author) + + # This preloads the number of warnings for every pipeline, ensuring + # that Ci::Pipeline#has_warnings? doesn't execute any additional + # queries. + pipeline.number_of_warnings + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb new file mode 100644 index 00000000000..477c7477df0 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Preloader do + describe '.preload' do + it 'preloads the author of every pipeline commit' do + commit = double(:commit) + pipeline = double(:pipeline, commit: commit) + + expect(commit) + .to receive(:lazy_author) + + expect(pipeline) + .to receive(:number_of_warnings) + + described_class.preload([pipeline]) + end + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ddd66a6be87..e7845b693a1 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -774,6 +774,33 @@ describe Ci::Pipeline, :mailer do end end + describe '#number_of_warnings' do + it 'returns the number of warnings' do + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') + + expect(pipeline.number_of_warnings).to eq(1) + end + + it 'supports eager loading of the number of warnings' do + pipeline2 = create(:ci_empty_pipeline, status: :created, project: project) + + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop') + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline2, name: 'rubocop') + + pipelines = project.pipelines.to_a + + pipelines.each(&:number_of_warnings) + + # To run the queries we need to actually use the lazy objects, which we do + # by just sending "to_i" to them. + amount = ActiveRecord::QueryRecorder + .new { pipelines.each { |p| p.number_of_warnings.to_i } } + .count + + expect(amount).to eq(1) + end + end + shared_context 'with some outdated pipelines' do before do create_pipeline(:canceled, 'ref', 'A', project) diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 448b813c2e1..090f91168ad 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -52,22 +52,98 @@ describe Commit do end end - describe '#author' do + describe '#author', :request_store do it 'looks up the author in a case-insensitive way' do user = create(:user, email: commit.author_email.upcase) expect(commit.author).to eq(user) end - it 'caches the author', :request_store do + it 'caches the author' do user = create(:user, email: commit.author_email) - expect(User).to receive(:find_by_any_email).and_call_original expect(commit.author).to eq(user) + key = "Commit:author:#{commit.author_email.downcase}" - expect(RequestStore.store[key]).to eq(user) + expect(RequestStore.store[key]).to eq(user) expect(commit.author).to eq(user) end + + context 'using eager loading' do + let!(:alice) { create(:user, email: 'alice@example.com') } + let!(:bob) { create(:user, email: 'hunter2@example.com') } + + let(:alice_commit) do + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'alice@example.com' + end + end + + let(:bob_commit) do + # The commit for Bob uses one of his alternative Emails, instead of the + # primary one. + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'bob@example.com' + end + end + + let(:eve_commit) do + described_class.new(RepoHelpers.sample_commit, project).tap do |c| + c.author_email = 'eve@example.com' + end + end + + let!(:commits) { [alice_commit, bob_commit, eve_commit] } + + before do + create(:email, user: bob, email: 'bob@example.com') + end + + it 'executes only two SQL queries' do + recorder = ActiveRecord::QueryRecorder.new do + # Running this first ensures we don't run one query for every + # commit. + commits.each(&:lazy_author) + + # This forces the execution of the SQL queries necessary to load the + # data. + commits.each { |c| c.author.try(:id) } + end + + expect(recorder.count).to eq(2) + end + + it "preloads the authors for Commits matching a user's primary Email" do + commits.each(&:lazy_author) + + expect(alice_commit.author).to eq(alice) + end + + it "preloads the authors for Commits using a User's alternative Email" do + commits.each(&:lazy_author) + + expect(bob_commit.author).to eq(bob) + end + + it 'sets the author to Nil if an author could not be found for a Commit' do + commits.each(&:lazy_author) + + expect(eve_commit.author).to be_nil + end + + it 'does not execute SQL queries once the authors are preloaded' do + commits.each(&:lazy_author) + commits.each { |c| c.author.try(:id) } + + recorder = ActiveRecord::QueryRecorder.new do + alice_commit.author + bob_commit.author + eve_commit.author + end + + expect(recorder.count).to be_zero + end + end end describe '#to_reference' do -- cgit v1.2.3 From 878ca2e69b371e6c12acee6bddd32b4406c651d7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 11 May 2018 16:17:03 +0200 Subject: Exclude coverage data from the pipelines page When displaying a project's pipelines (Projects::PipelinesController#index) we now exclude the coverage data. This data was not used by the frontend, yet getting it would require one SQL query per pipeline. These queries in turn could be quite expensive on GitLab.com. --- app/controllers/projects/pipelines_controller.rb | 2 +- app/serializers/pipeline_entity.rb | 6 +++++- spec/controllers/projects/pipelines_controller_spec.rb | 6 ++++++ spec/serializers/pipeline_entity_spec.rb | 7 +++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 91afe24b707..6b40fc2fe68 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -34,7 +34,7 @@ class Projects::PipelinesController < Projects::ApplicationController pipelines: PipelineSerializer .new(project: @project, current_user: @current_user) .with_pagination(request, response) - .represent(@pipelines), + .represent(@pipelines, disable_coverage: true), count: { all: @pipelines_count, running: @running_count, diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6457294b285..f782b411b84 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -4,7 +4,11 @@ class PipelineEntity < Grape::Entity expose :id expose :user, using: UserEntity expose :active?, as: :active - expose :coverage + + # Coverage isn't always necessary (e.g. when displaying project pipelines in + # the UI). Instead of creating an entirely different entity we just allow the + # disabling of this specific field whenever necessary. + expose :coverage, unless: proc { options[:disable_coverage] } expose :source expose :created_at, :updated_at diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index c22e455ea2d..9e7bc20a6d1 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -41,6 +41,12 @@ describe Projects::PipelinesController do expect(json_response['count']['finished']).to eq '1' end + it 'does not include coverage data for the pipelines' do + subject + + expect(json_response['pipelines'][0]).not_to include('coverage') + end + context 'when performing gitaly calls', :request_store do it 'limits the Gitaly requests' do expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(3) diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 2473c561f4b..e67d12b7a89 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -26,6 +26,13 @@ describe PipelineEntity do expect(subject).to include :updated_at, :created_at end + it 'excludes coverage data when disabled' do + entity = described_class + .represent(pipeline, request: request, disable_coverage: true) + + expect(entity.as_json).not_to include(:coverage) + end + it 'contains details' do expect(subject).to include :details expect(subject[:details]) -- cgit v1.2.3 From da7bbef8fed767483236f9503553942b4c1acd05 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 11 May 2018 16:22:09 +0200 Subject: Added changelog for pipelines page performance --- changelogs/unreleased/pipelines-index-performance.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/pipelines-index-performance.yml diff --git a/changelogs/unreleased/pipelines-index-performance.yml b/changelogs/unreleased/pipelines-index-performance.yml new file mode 100644 index 00000000000..928c2ddab72 --- /dev/null +++ b/changelogs/unreleased/pipelines-index-performance.yml @@ -0,0 +1,5 @@ +--- +title: Improve performance of project pipelines pages +merge_request: +author: +type: performance -- cgit v1.2.3 From 143a98a3054644f70fbcc725c453f443c4546e3c Mon Sep 17 00:00:00 2001 From: Fabio Busatto Date: Thu, 17 May 2018 12:57:49 +0000 Subject: Update quick_start_guide.md --- doc/topics/autodevops/quick_start_guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index 15567715c98..0b16af2953b 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -23,6 +23,10 @@ page](https://gitlab.com/auto-devops-examples/minimal-ruby-app) and press the **Fork** button. Soon you should have a project under your namespace with the necessary files. +You can also start a new project from a +[GitLab project template](https://gitlab.com/gitlab-org/project-templates) if +you want to use a different language. + ## Setup your own cluster on Google Kubernetes Engine If you do not already have a Google Cloud account, create one at -- cgit v1.2.3 From 368697ecf343c51db4f1a36372a2aa1ce5f950a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 17 May 2018 15:23:04 +0200 Subject: Loosen the matcher in the MR creation test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- qa/qa/specs/features/merge_request/create_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/qa/specs/features/merge_request/create_spec.rb b/qa/qa/specs/features/merge_request/create_spec.rb index fbf9a4d17e5..0931e649e24 100644 --- a/qa/qa/specs/features/merge_request/create_spec.rb +++ b/qa/qa/specs/features/merge_request/create_spec.rb @@ -11,7 +11,7 @@ module QA 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') + expect(page).to have_content(/Opened [\w\s]+ a minute ago/) end end end -- cgit v1.2.3 From 22d3e90ced30532ede17509d373c2e88257a7fe1 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Thu, 17 May 2018 08:50:52 -0500 Subject: Add changelog --- changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml diff --git a/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml b/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml new file mode 100644 index 00000000000..2f885c5c927 --- /dev/null +++ b/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml @@ -0,0 +1,5 @@ +--- +title: Allow CommitStatus class to use presentable methods +merge_request: 18979 +author: +type: fixed -- cgit v1.2.3 From 88fa0ecdd9a2982795bb9ddd56873e6ac5c23815 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 17 May 2018 12:59:20 +0000 Subject: Fix group lists visual * Reset p bottom margin for group lists to fix vertical alignment * Remove double border for group lists to be consistent with project lists Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 11 +---------- app/assets/stylesheets/pages/groups.scss | 4 ++++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index f1a8a46dda4..4110d7f15a8 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -279,7 +279,6 @@ ul.indent-list { padding: 10px 0 0 30px; } - // Specific styles for tree list @keyframes spin-avatar { from { transform: rotate(0deg); } @@ -424,18 +423,10 @@ ul.indent-list { &:first-child { border-top: 1px solid $white-normal; } - - &:last-of-type { - .group-row-contents:not(:hover) { - border-bottom: 1px solid transparent; - } - } } .group-row-contents { - padding: 10px 10px 8px; - border-top: solid 1px transparent; - border-bottom: solid 1px $white-normal; + padding: $gl-padding-top; &:hover { border-color: $row-hover-border; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 6ee8b33bd39..c378ad50836 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -18,6 +18,10 @@ .group-row { @include basic-list-stats; + + .description p { + margin-bottom: 0; + } } .ldap-group-links { -- cgit v1.2.3 From b11c218ad9d4635c2230e7f8d105236ca32e5072 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Thu, 17 May 2018 16:20:41 +0000 Subject: Fix: Use case in-sensitive ordering by name for groups --- app/models/concerns/sortable.rb | 4 +- ...-insensitive-ordering-for-dashboard-filters.yml | 5 + .../20180504195842_project_name_lower_index.rb | 32 ++++++ lib/tasks/migrate/setup_postgresql.rake | 2 + spec/models/concerns/sortable_spec.rb | 108 +++++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/use-case-insensitive-ordering-for-dashboard-filters.yml create mode 100644 db/migrate/20180504195842_project_name_lower_index.rb create mode 100644 spec/models/concerns/sortable_spec.rb diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index cefa5c13c5f..db7254c27e0 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -12,8 +12,8 @@ module Sortable scope :order_created_asc, -> { reorder(created_at: :asc) } scope :order_updated_desc, -> { reorder(updated_at: :desc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) } - scope :order_name_asc, -> { reorder(name: :asc) } - scope :order_name_desc, -> { reorder(name: :desc) } + scope :order_name_asc, -> { reorder("lower(name) asc") } + scope :order_name_desc, -> { reorder("lower(name) desc") } end module ClassMethods diff --git a/changelogs/unreleased/use-case-insensitive-ordering-for-dashboard-filters.yml b/changelogs/unreleased/use-case-insensitive-ordering-for-dashboard-filters.yml new file mode 100644 index 00000000000..098e4b1d5fa --- /dev/null +++ b/changelogs/unreleased/use-case-insensitive-ordering-for-dashboard-filters.yml @@ -0,0 +1,5 @@ +--- +title: "Use case in-sensitive ordering by name for dashboard" +merge_request: 18553 +author: "@vedharish" +type: fixed diff --git a/db/migrate/20180504195842_project_name_lower_index.rb b/db/migrate/20180504195842_project_name_lower_index.rb new file mode 100644 index 00000000000..d6f25d3d4ab --- /dev/null +++ b/db/migrate/20180504195842_project_name_lower_index.rb @@ -0,0 +1,32 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ProjectNameLowerIndex < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + INDEX_NAME = 'index_projects_on_lower_name' + + disable_ddl_transaction! + + def up + return unless Gitlab::Database.postgresql? + + disable_statement_timeout + + execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON projects (LOWER(name))" + end + + def down + return unless Gitlab::Database.postgresql? + + disable_statement_timeout + + if supports_drop_index_concurrently? + execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}" + else + execute "DROP INDEX IF EXISTS #{INDEX_NAME}" + end + end +end diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index af30ecb0e9b..e7aab50e42a 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -8,6 +8,7 @@ task setup_postgresql: :environment do 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/20180215181245_users_name_lower_index.rb') + require Rails.root.join('db/migrate/20180504195842_project_name_lower_index.rb') require Rails.root.join('db/post_migrate/20180306164012_add_path_index_to_redirect_routes.rb') NamespacesProjectsPathLowerIndexes.new.up @@ -18,5 +19,6 @@ task setup_postgresql: :environment do IndexRedirectRoutesPathForLike.new.up AddIndexOnNamespacesLowerName.new.up UsersNameLowerIndex.new.up + ProjectNameLowerIndex.new.up AddPathIndexToRedirectRoutes.new.up end diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb new file mode 100644 index 00000000000..b821a84d5e0 --- /dev/null +++ b/spec/models/concerns/sortable_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +describe Sortable do + describe '.order_by' do + let(:relation) { Group.all } + + describe 'ordering by id' do + it 'ascending' do + expect(relation).to receive(:reorder).with(id: :asc) + + relation.order_by('id_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(id: :desc) + + relation.order_by('id_desc') + end + end + + describe 'ordering by created day' do + it 'ascending' do + expect(relation).to receive(:reorder).with(created_at: :asc) + + relation.order_by('created_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(created_at: :desc) + + relation.order_by('created_desc') + end + + it 'order by "date"' do + expect(relation).to receive(:reorder).with(created_at: :desc) + + relation.order_by('created_date') + end + end + + describe 'ordering by name' do + it 'ascending' do + expect(relation).to receive(:reorder).with("lower(name) asc") + + relation.order_by('name_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with("lower(name) desc") + + relation.order_by('name_desc') + end + end + + describe 'ordering by Updated Time' do + it 'ascending' do + expect(relation).to receive(:reorder).with(updated_at: :asc) + + relation.order_by('updated_asc') + end + + it 'descending' do + expect(relation).to receive(:reorder).with(updated_at: :desc) + + relation.order_by('updated_desc') + end + end + + it 'does not call reorder in case of unrecognized ordering' do + expect(relation).not_to receive(:reorder) + + relation.order_by('random_ordering') + end + end + + describe 'sorting groups' do + def ordered_group_names(order) + Group.all.order_by(order).map(&:name) + end + + let!(:ref_time) { Time.parse('2018-05-01 00:00:00') } + let!(:group1) { create(:group, name: 'aa', id: 1, created_at: ref_time - 15.seconds, updated_at: ref_time) } + let!(:group2) { create(:group, name: 'AAA', id: 2, created_at: ref_time - 10.seconds, updated_at: ref_time - 5.seconds) } + let!(:group3) { create(:group, name: 'BB', id: 3, created_at: ref_time - 5.seconds, updated_at: ref_time - 10.seconds) } + let!(:group4) { create(:group, name: 'bbb', id: 4, created_at: ref_time, updated_at: ref_time - 15.seconds) } + + it 'sorts groups by id' do + expect(ordered_group_names('id_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('id_desc')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by name via case-insentitive comparision' do + expect(ordered_group_names('name_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('name_desc')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by created_at' do + expect(ordered_group_names('created_asc')).to eq(%w(aa AAA BB bbb)) + expect(ordered_group_names('created_desc')).to eq(%w(bbb BB AAA aa)) + expect(ordered_group_names('created_date')).to eq(%w(bbb BB AAA aa)) + end + + it 'sorts groups by updated_at' do + expect(ordered_group_names('updated_asc')).to eq(%w(bbb BB AAA aa)) + expect(ordered_group_names('updated_desc')).to eq(%w(aa AAA BB bbb)) + end + end +end -- cgit v1.2.3 From e4adf0150b58d0b7f8437cbb9a3cb3ac8aa31bec Mon Sep 17 00:00:00 2001 From: Jacopo Date: Tue, 15 May 2018 12:40:17 +0200 Subject: Fixes 500 error on /estimate BIG_VALUE --- app/models/concerns/time_trackable.rb | 4 ++++ changelogs/unreleased/46193-fix-big-estimate.yml | 5 +++++ spec/models/concerns/issuable_spec.rb | 13 +++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 changelogs/unreleased/46193-fix-big-estimate.yml diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index 73fc5048dcf..1caf47072bc 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -53,6 +53,10 @@ module TimeTrackable Gitlab::TimeTrackingFormatter.output(time_estimate) end + def time_estimate=(val) + val.is_a?(Integer) ? super([val, Gitlab::Database::MAX_INT_VALUE].min) : super(val) + end + private def touchable? diff --git a/changelogs/unreleased/46193-fix-big-estimate.yml b/changelogs/unreleased/46193-fix-big-estimate.yml new file mode 100644 index 00000000000..d0da0c10033 --- /dev/null +++ b/changelogs/unreleased/46193-fix-big-estimate.yml @@ -0,0 +1,5 @@ +--- +title: Fixes 500 error on /estimate BIG_VALUE +merge_request: 18964 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 3d3092b8ac9..bd6bf5b0712 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -266,6 +266,19 @@ describe Issuable do end end + describe '#time_estimate=' do + it 'coerces the value below Gitlab::Database::MAX_INT_VALUE' do + expect { issue.time_estimate = 100 }.to change { issue.time_estimate }.to(100) + expect { issue.time_estimate = Gitlab::Database::MAX_INT_VALUE + 100 }.to change { issue.time_estimate }.to(Gitlab::Database::MAX_INT_VALUE) + end + + it 'skips coercion for not Integer values' do + expect { issue.time_estimate = nil }.to change { issue.time_estimate }.to(nil) + expect { issue.time_estimate = 'invalid time' }.not_to raise_error(StandardError) + expect { issue.time_estimate = 22.33 }.not_to raise_error(StandardError) + end + end + describe '#to_hook_data' do let(:builder) { double } -- cgit v1.2.3 From bd2b57d20bc92db8fa601abcc8360d37c94a716c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 17 May 2018 11:43:54 -0500 Subject: Bring CE-EE parity to app/services/lfs/unlock_file_service.rb --- app/services/lfs/unlock_file_service.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/lfs/unlock_file_service.rb b/app/services/lfs/unlock_file_service.rb index 6c93dc69bb0..7eb89339a92 100644 --- a/app/services/lfs/unlock_file_service.rb +++ b/app/services/lfs/unlock_file_service.rb @@ -2,14 +2,14 @@ module Lfs class UnlockFileService < BaseService def execute unless can?(current_user, :push_code, project) - raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions' + raise Gitlab::GitAccess::UnauthorizedError, _('You have no permissions') end unlock_file rescue Gitlab::GitAccess::UnauthorizedError => ex error(ex.message, 403) rescue ActiveRecord::RecordNotFound - error('Lock not found', 404) + error(_('Lock not found'), 404) rescue => ex error(ex.message, 500) end @@ -24,9 +24,9 @@ module Lfs success(lock: lock, http_status: :ok) elsif forced - error('You must have master access to force delete a lock', 403) + error(_('You must have master access to force delete a lock'), 403) else - error("#{lock.path} is locked by GitLab User #{lock.user_id}", 403) + error(_("%{lock_path} is locked by GitLab User %{lock_user_id}") % { lock_path: lock.path, lock_user_id: lock.user_id }, 403) end end -- cgit v1.2.3 From d1f44952da9e45d90a6815bd3630093de9eaea66 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 17 May 2018 11:51:55 -0500 Subject: Bring CE-EE parity to app/services/milestones/base_service.rb --- app/services/milestones/base_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/milestones/base_service.rb b/app/services/milestones/base_service.rb index 4963601ea8b..cce0863d611 100644 --- a/app/services/milestones/base_service.rb +++ b/app/services/milestones/base_service.rb @@ -5,6 +5,7 @@ module Milestones def initialize(parent, user, params = {}) @parent, @current_user, @params = parent, user, params.dup + super end end end -- cgit v1.2.3 From 90100c26611216ac9b1c64c4ed54a8b5a20df5c9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 17 May 2018 10:12:48 -0700 Subject: Conditionally add Gitaly deprecation warnings based on ENV variable --- config/initializers/deprecations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb index c8d7f742bb1..14616e726d9 100644 --- a/config/initializers/deprecations.rb +++ b/config/initializers/deprecations.rb @@ -1,4 +1,4 @@ -if Gitlab.dev_env_or_com? +if Rails.env.development? || ENV['GITLAB_LEGACY_PATH_LOG_MESSAGE'] deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab') deprecator.behavior = -> (message, callstack) { -- cgit v1.2.3 From 63c58a6dd0d8a4b3db172ffe6d0e32e127cdabd1 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 17 May 2018 21:20:15 +0200 Subject: Memoize Gitlab::Database.version This removes the need for running a database query every time we want to check the database version. --- changelogs/unreleased/memoize-database-version.yml | 5 +++++ lib/gitlab/database.rb | 2 +- spec/lib/gitlab/database_spec.rb | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/memoize-database-version.yml diff --git a/changelogs/unreleased/memoize-database-version.yml b/changelogs/unreleased/memoize-database-version.yml new file mode 100644 index 00000000000..575348a53a1 --- /dev/null +++ b/changelogs/unreleased/memoize-database-version.yml @@ -0,0 +1,5 @@ +--- +title: Memoize Gitlab::Database.version +merge_request: +author: +type: performance diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 76501dd50e8..d49d055c3f2 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -43,7 +43,7 @@ module Gitlab end def self.version - database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1] + @version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1] end def self.join_lateral_supported? diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 1fe1d3926ad..8ac36ae8bab 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -32,6 +32,12 @@ describe Gitlab::Database do end describe '.version' do + around do |example| + described_class.instance_variable_set(:@version, nil) + example.run + described_class.instance_variable_set(:@version, nil) + end + context "on mysql" do it "extracts the version number" do allow(described_class).to receive(:database_version) @@ -49,6 +55,14 @@ describe Gitlab::Database do expect(described_class.version).to eq '9.4.4' end end + + it 'memoizes the result' do + count = ActiveRecord::QueryRecorder + .new { 2.times { described_class.version } } + .count + + expect(count).to eq(1) + end end describe '.join_lateral_supported?' do -- cgit v1.2.3 From 3babd1e0cb7173e09df1844f82427102c4104ba9 Mon Sep 17 00:00:00 2001 From: Murat Dogan Date: Thu, 17 May 2018 19:36:56 +0000 Subject: fix / assigne username wrapping problem has been fixed --- app/assets/stylesheets/pages/issuable.scss | 18 +++++++++++++++--- changelogs/unreleased/fix-assignee-name-wrap.yml | 5 +++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/fix-assignee-name-wrap.yml diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index b2dad4a358a..a8110f069d4 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -197,9 +197,21 @@ } &.assignee { - .author_link:hover { - .author { - text-decoration: underline; + .author_link { + display: block; + padding-left: 42px; + position: relative; + + &:hover { + .author { + text-decoration: underline; + } + } + + .avatar { + left: 0; + position: absolute; + top: 0; } } } diff --git a/changelogs/unreleased/fix-assignee-name-wrap.yml b/changelogs/unreleased/fix-assignee-name-wrap.yml new file mode 100644 index 00000000000..2407288785f --- /dev/null +++ b/changelogs/unreleased/fix-assignee-name-wrap.yml @@ -0,0 +1,5 @@ +--- +title: Wrapping problem on the issues page has been fixed +merge_request: +author: +type: fixed -- cgit v1.2.3 From 35b37cfbc39cac341ed4caec5c4baaf0646a4b66 Mon Sep 17 00:00:00 2001 From: Sarrah Vesselov Date: Thu, 17 May 2018 20:29:41 +0000 Subject: fix typos. add a reference to deliverable and stretch for design artifact --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 758c2a9ea6c..7f28da092cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -168,7 +168,7 @@ hits. They are not always necessary, but very convenient. If you are an expert in a particular area, it makes it easier to find issues to work on. You can also subscribe to those labels to receive an email each time an -issue is labelled with a subject label corresponding to your expertise. +issue is labeled with a subject label corresponding to your expertise. Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api, ~issues, ~"merge requests", ~labels, and ~"container registry". @@ -315,10 +315,10 @@ Once an issue has been worked on and is ready for development, a UXer applies th The UX team has a special type label called ~"design artifact". This label indicates that the final output for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone. -Any issue labelled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is +Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is needed until the solution has been decided. -~"design artifact" issues are like any other issue and should contain a milestone label when scheduled in the current milestone. +~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone. Once the ~"design artifact" issue has been completed, the UXer removes the ~"design artifact" label and applies the ~"UX ready" label. The Product Manager can use the existing issue or decide to create a whole new issue for the purpose of development. -- cgit v1.2.3 From 0bc78d08000ae1d5ee0943c8991d6d3fee7977e3 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Thu, 17 May 2018 14:10:37 -0700 Subject: Build cloud native images on tags When on a tag, trigger a multi-project pipeline in the CNG repostiory. Opting for a trigger rather than an addition to our release-tools project for a few reasons: - The Dockerfiles in the CNG image repo change infrequently, and as a result I don't feel the need/overhead for stable branches in that repo at this time - My intent with the CNG repo, is that once stable, the Dockerfiles would actualy move to their component projects, to be versioned with the code they are building - It is likely that we will want to followup with a manually triggered package for branches for devs, and possibly review apps, so it made sense to build the CNG ci jobs to accept this sort of pipeline. --- .gitlab-ci.yml | 19 ++++++++++++ scripts/trigger-build-cloud-native | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100755 scripts/trigger-build-cloud-native diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84d8e69b84e..cc6fd5e2bfe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -298,6 +298,25 @@ review-docs-cleanup: script: - ./trigger-build-docs cleanup +## +# Trigger a docker image build in CNG (Cloud Native GitLab) repository +# +cloud-native-image: + image: ruby:2.4-alpine + before_script: [] + stage: build + allow_failure: true + cache: {} + retry: 0 + before_script: + - gem install gitlab --no-doc + - chmod 755 ./scripts/trigger-build-cloud-native + script: + - ./scripts/trigger-build-cloud-native + only: + - tags@gitlab-org/gitlab-ce + - tags@gitlab-org/gitlab-ee + # Retrieve knapsack and rspec_flaky reports retrieve-tests-metadata: <<: *tests-metadata-state diff --git a/scripts/trigger-build-cloud-native b/scripts/trigger-build-cloud-native new file mode 100755 index 00000000000..b6ca75a588d --- /dev/null +++ b/scripts/trigger-build-cloud-native @@ -0,0 +1,61 @@ +#!/usr/bin/env ruby + +require 'gitlab' + +# +# Configure credentials to be used with gitlab gem +# +Gitlab.configure do |config| + config.endpoint = 'https://gitlab.com/api/v4' +end + +# +# The remote project +# +GITLAB_CNG_REPO = 'gitlab-org/build/CNG'.freeze + +def ee? + ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md') +end + +def read_file_version(filename) + raw_version = File.read(filename).strip + + # if the version matches semver format, treat it as a tag and prepend `v` + if raw_version =~ Regexp.compile(/^\d+\.\d+\.\d+(-rc\d+)?(-ee)?$/) + "v#{raw_version}" + else + raw_version + end +end + +def params + params = { + 'GITLAB_SHELL_VERSION' => read_file_version('GITLAB_SHELL_VERSION'), + 'GITALY_VERSION' => read_file_version('GITALY_SERVER_VERSION'), + 'TRIGGERED_USER' => ENV['GITLAB_USER_NAME'], + 'TRIGGER_SOURCE' => "https://gitlab.com/gitlab-org/#{ENV['CI_PROJECT_NAME']}/-/jobs/#{ENV['CI_JOB_ID']}" + } + + if ee? + params['EE_PIPELINE'] = 'true' + params['GITLAB_EE_VERSION'] = ENV['CI_COMMIT_REF_NAME'] + else + params['CE_PIPELINE'] = 'true' + params['GITLAB_CE_VERSION'] = ENV['CI_COMMIT_REF_NAME'] + end + + params +end + +# +# Trigger a pipeline +# +def trigger_pipeline + # Create the cross project pipeline using CI_JOB_TOKEN + pipeline = Gitlab.run_trigger(GITLAB_CNG_REPO, ENV['CI_JOB_TOKEN'], 'master', params) + + puts "Triggered https://gitlab.com/#{GITLAB_CNG_REPO}/pipelines/#{pipeline.id}" +end + +trigger_pipeline -- cgit v1.2.3 From b6d1e20c918608844cd8438c73120016a6dab2d4 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Thu, 17 May 2018 18:27:10 -0400 Subject: Update CICD to use rocket icon --- app/views/layouts/nav/sidebar/_project.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index c3ea592a6b5..4d7a5a6e856 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -154,7 +154,7 @@ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do .nav-icon-container - = sprite_icon('pipeline') + = sprite_icon('rocket') %span.nav-item-name = _('CI / CD') -- cgit v1.2.3 From 6c190d273d18d21e50dea65645185839bf067714 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 18 May 2018 01:05:11 +0000 Subject: Move API group deletion to Sidekiq --- changelogs/unreleased/sh-move-delete-groups-api-async.yml | 5 +++++ doc/api/groups.md | 3 +++ lib/api/groups.rb | 4 +++- lib/api/v3/groups.rb | 5 +++-- spec/requests/api/groups_spec.rb | 9 ++++++--- spec/requests/api/v3/groups_spec.rb | 8 +++++--- 6 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/sh-move-delete-groups-api-async.yml diff --git a/changelogs/unreleased/sh-move-delete-groups-api-async.yml b/changelogs/unreleased/sh-move-delete-groups-api-async.yml new file mode 100644 index 00000000000..1b200cac5c5 --- /dev/null +++ b/changelogs/unreleased/sh-move-delete-groups-api-async.yml @@ -0,0 +1,5 @@ +--- +title: Move API group deletion to Sidekiq +merge_request: +author: +type: changed diff --git a/doc/api/groups.md b/doc/api/groups.md index 923fd662a5b..96842ef330f 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -487,6 +487,9 @@ Parameters: - `id` (required) - The ID or path of a user group +This will queue a background job to delete all projects in the group. The +response will be a 202 Accepted if the user has authorization. + ## Search for group Get all groups that match your string in their name or path. diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 0d125cd7831..03b6b30a0d8 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -167,8 +167,10 @@ module API Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/46285') destroy_conditionally!(group) do |group| - ::Groups::DestroyService.new(group, current_user).execute + ::Groups::DestroyService.new(group, current_user).async_execute end + + accepted! end desc 'Get a list of projects in this group.' do diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb index 3844fd4810d..4fa7d196e50 100644 --- a/lib/api/v3/groups.rb +++ b/lib/api/v3/groups.rb @@ -131,8 +131,9 @@ module API delete ":id" do group = find_group!(params[:id]) authorize! :admin_group, group - Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/46285') - present ::Groups::DestroyService.new(group, current_user).execute, with: Entities::GroupDetail, current_user: current_user + ::Groups::DestroyService.new(group, current_user).async_execute + + accepted! end desc 'Get a list of projects in this group.' do diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index bb0034e3237..7d923932309 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -738,13 +738,16 @@ describe API::Groups do describe "DELETE /groups/:id" do context "when authenticated as user" do it "removes group" do - delete api("/groups/#{group1.id}", user1) + Sidekiq::Testing.fake! do + expect { delete api("/groups/#{group1.id}", user1) }.to change(GroupDestroyWorker.jobs, :size).by(1) + end - expect(response).to have_gitlab_http_status(204) + expect(response).to have_gitlab_http_status(202) end it_behaves_like '412 response' do let(:request) { api("/groups/#{group1.id}", user1) } + let(:success_status) { 202 } end it "does not remove a group if not an owner" do @@ -773,7 +776,7 @@ describe API::Groups do it "removes any existing group" do delete api("/groups/#{group2.id}", admin) - expect(response).to have_gitlab_http_status(204) + expect(response).to have_gitlab_http_status(202) end it "does not remove a non existing group" do diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index a1cdf583de3..34d4b8e9565 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -458,9 +458,11 @@ describe API::V3::Groups do describe "DELETE /groups/:id" do context "when authenticated as user" do it "removes group" do - delete v3_api("/groups/#{group1.id}", user1) + Sidekiq::Testing.fake! do + expect { delete v3_api("/groups/#{group1.id}", user1) }.to change(GroupDestroyWorker.jobs, :size).by(1) + end - expect(response).to have_gitlab_http_status(200) + expect(response).to have_gitlab_http_status(202) end it "does not remove a group if not an owner" do @@ -489,7 +491,7 @@ describe API::V3::Groups do it "removes any existing group" do delete v3_api("/groups/#{group2.id}", admin) - expect(response).to have_gitlab_http_status(200) + expect(response).to have_gitlab_http_status(202) end it "does not remove a non existing group" do -- cgit v1.2.3 From b5821e5fb3882a744ce4f30367859c5cff66d765 Mon Sep 17 00:00:00 2001 From: Lukas Eipert Date: Fri, 18 May 2018 02:18:42 +0000 Subject: Add Keyboard shortcuts for "Kubernetes" and "Environments" --- app/assets/javascripts/shortcuts_navigation.js | 5 +-- app/views/help/_shortcuts.html.haml | 14 +++++++- app/views/layouts/nav/sidebar/_project.html.haml | 2 +- .../46427-add-keyboard-shortcut-environments.yml | 5 +++ .../46427-add-keyboard-shortcut-kubernetes.yml | 5 +++ ...7-change-keyboard-shortcut-of-activity-feed.yml | 5 +++ .../46427-remove-outdated-todos-shortcut.yml | 5 +++ doc/workflow/shortcuts.md | 10 ++++-- spec/features/projects/user_uses_shortcuts_spec.rb | 41 +++++++++++++++++++++- 9 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/46427-add-keyboard-shortcut-environments.yml create mode 100644 changelogs/unreleased/46427-add-keyboard-shortcut-kubernetes.yml create mode 100644 changelogs/unreleased/46427-change-keyboard-shortcut-of-activity-feed.yml create mode 100644 changelogs/unreleased/46427-remove-outdated-todos-shortcut.yml diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index a4d10850471..78f7353eb0d 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -7,7 +7,7 @@ export default class ShortcutsNavigation extends Shortcuts { super(); Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project')); - Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity')); + Mousetrap.bind('g v', () => findAndFollowLink('.shortcuts-project-activity')); Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree')); Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits')); Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds')); @@ -16,9 +16,10 @@ export default class ShortcutsNavigation extends Shortcuts { Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues')); Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards')); Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests')); - Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos')); Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki')); Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets')); + Mousetrap.bind('g k', () => findAndFollowLink('.shortcuts-kubernetes')); + Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-environments')); Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue')); this.enabledHelp.push('.hidden-shortcut.project'); diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 1c5b4aecabb..2244d16f0a6 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -121,7 +121,7 @@ %tr %td.shortcut .key g - .key e + .key v %td Go to the project's activity feed %tr @@ -172,6 +172,18 @@ .key m %td Go to merge requests + %tr + %td.shortcut + .key g + .key e + %td + Go to environments + %tr + %td.shortcut + .key g + .key k + %td + Go to kubernetes %tr %td.shortcut .key g diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index c3ea592a6b5..0023ede2be9 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -212,7 +212,7 @@ - if project_nav_tab? :clusters - show_cluster_hint = show_gke_cluster_integration_callout?(@project) = nav_link(controller: [:clusters, :user, :gcp]) do - = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-cluster' do + = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do %span = _('Kubernetes') - if show_cluster_hint diff --git a/changelogs/unreleased/46427-add-keyboard-shortcut-environments.yml b/changelogs/unreleased/46427-add-keyboard-shortcut-environments.yml new file mode 100644 index 00000000000..609968f3230 --- /dev/null +++ b/changelogs/unreleased/46427-add-keyboard-shortcut-environments.yml @@ -0,0 +1,5 @@ +--- +title: Adds keyboard shortcut `g e` for Environments on Project pages +merge_request: 19002 +author: +type: added diff --git a/changelogs/unreleased/46427-add-keyboard-shortcut-kubernetes.yml b/changelogs/unreleased/46427-add-keyboard-shortcut-kubernetes.yml new file mode 100644 index 00000000000..48e51b2615e --- /dev/null +++ b/changelogs/unreleased/46427-add-keyboard-shortcut-kubernetes.yml @@ -0,0 +1,5 @@ +--- +title: Adds keyboard shortcut `g k` for Kubernetes on Project pages +merge_request: 19002 +author: +type: added diff --git a/changelogs/unreleased/46427-change-keyboard-shortcut-of-activity-feed.yml b/changelogs/unreleased/46427-change-keyboard-shortcut-of-activity-feed.yml new file mode 100644 index 00000000000..9a7cf0d6944 --- /dev/null +++ b/changelogs/unreleased/46427-change-keyboard-shortcut-of-activity-feed.yml @@ -0,0 +1,5 @@ +--- +title: Changes keyboard shortcut of Activity feed to `g v` +merge_request: 19002 +author: +type: changed diff --git a/changelogs/unreleased/46427-remove-outdated-todos-shortcut.yml b/changelogs/unreleased/46427-remove-outdated-todos-shortcut.yml new file mode 100644 index 00000000000..f416e35030e --- /dev/null +++ b/changelogs/unreleased/46427-remove-outdated-todos-shortcut.yml @@ -0,0 +1,5 @@ +--- +title: Removes outdated `g t` shortcut for TODO in favor of `Shift+T` +merge_request: 19002 +author: +type: removed diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 2e1bd6bfe5c..930f2e73683 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -46,15 +46,19 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | Keyboard Shortcut | Description | | ----------------- | ----------- | | g + p | Go to the project's home page | -| g + e | Go to the project's activity feed | +| g + v | Go to the project's activity feed | | g + f | Go to files | | g + c | Go to commits | -| g + b | Go to jobs | +| g + j | Go to jobs | | g + n | Go to network graph | -| g + g | Go to repository charts | +| g + d | Go to repository charts | | g + i | Go to issues | +| g + b | Go to issue boards | | g + m | Go to merge requests | +| g + e | Go to environments | +| g + k | Go to kubernetes | | g + s | Go to snippets | +| g + w | Go to wiki | | t | Go to finding file | | i | New issue | diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index 47c5a8161d9..495a010b32c 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -13,6 +13,8 @@ describe 'User uses shortcuts', :js do context 'when navigating to the Project pages' do it 'redirects to the details page' do + visit project_issues_path(project) + find('body').native.send_key('g') find('body').native.send_key('p') @@ -22,7 +24,7 @@ describe 'User uses shortcuts', :js do it 'redirects to the activity page' do find('body').native.send_key('g') - find('body').native.send_key('e') + find('body').native.send_key('v') expect(page).to have_active_navigation('Project') expect(page).to have_active_sub_navigation('Activity') @@ -72,10 +74,19 @@ describe 'User uses shortcuts', :js do expect(page).to have_active_sub_navigation('List') end + it 'redirects to the issue board page' do + find('body').native.send_key('g') + find('body').native.send_key('b') + + expect(page).to have_active_navigation('Issues') + expect(page).to have_active_sub_navigation('Board') + end + it 'redirects to the new issue page' do find('body').native.send_key('i') expect(page).to have_content(project.title) + expect(page).to have_content('New Issue') end end @@ -88,6 +99,34 @@ describe 'User uses shortcuts', :js do end end + context 'when navigating to the CI / CD pages' do + it 'redirects to the Jobs page' do + find('body').native.send_key('g') + find('body').native.send_key('j') + + expect(page).to have_active_navigation('CI / CD') + expect(page).to have_active_sub_navigation('Jobs') + end + end + + context 'when navigating to the Operations pages' do + it 'redirects to the Environments page' do + find('body').native.send_key('g') + find('body').native.send_key('e') + + expect(page).to have_active_navigation('Operations') + expect(page).to have_active_sub_navigation('Environments') + end + + it 'redirects to the Kubernetes page' do + find('body').native.send_key('g') + find('body').native.send_key('k') + + expect(page).to have_active_navigation('Operations') + expect(page).to have_active_sub_navigation('Kubernetes') + end + end + context 'when navigating to the Snippets pages' do it 'redirects to the snippets page' do find('body').native.send_key('g') -- cgit v1.2.3 From db0454fe74776ef13ef1ad6b6ce688867d9a6c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Lu=C3=ADs?= Date: Fri, 18 May 2018 03:51:36 +0000 Subject: =?UTF-8?q?Resolve=20"Web=20IDE:=20Previewing=20Markdown=20in=20Fi?= =?UTF-8?q?refox=20doesn=E2=80=99t=20show=20a=20scroll=20bar"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/stylesheets/pages/repo.scss | 18 ++++++++++++------ .../unreleased/45934-ide-firefox-scroll-md-preview.yml | 5 +++++ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/45934-ide-firefox-scroll-md-preview.yml diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 00457717f00..7637a3f577c 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -306,8 +306,18 @@ } .preview-container { - height: 100%; - overflow: auto; + flex-grow: 1; + position: relative; + + .md-previewer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: auto; + padding: $gl-padding; + } .file-container { background-color: $gray-darker; @@ -347,10 +357,6 @@ color: $diff-image-info-color; } } - - .md-previewer { - padding: $gl-padding; - } } .ide-mode-tabs { diff --git a/changelogs/unreleased/45934-ide-firefox-scroll-md-preview.yml b/changelogs/unreleased/45934-ide-firefox-scroll-md-preview.yml new file mode 100644 index 00000000000..b9e70bc5679 --- /dev/null +++ b/changelogs/unreleased/45934-ide-firefox-scroll-md-preview.yml @@ -0,0 +1,5 @@ +--- +title: Fix unscrollable Markdown preview of WebIDE on Firefox +merge_request: +author: +type: fixed -- cgit v1.2.3 From d47d02acfb4e244a6c85b137253f865e2d463ae3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 18 May 2018 09:33:45 +0300 Subject: Move group lists css from framework/lists.scss to pages/groups.scss Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 228 ---------------------------- app/assets/stylesheets/pages/groups.scss | 228 ++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 228 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 4110d7f15a8..45517416e93 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -285,236 +285,8 @@ ul.indent-list { to { transform: rotate(360deg); } } -.groups-list-tree-container { - .has-no-search-results { - text-align: center; - padding: $gl-padding; - font-style: italic; - color: $well-light-text-color; - } - - > .group-list-tree > .group-row.has-children:first-child { - border-top: 0; - } -} - -.group-list-tree { - .avatar-container.content-loading { - position: relative; - - > a, - > a .avatar { - height: 100%; - border-radius: 50%; - } - - > a { - padding: 2px; - - .avatar { - border: 2px solid $white-normal; - - &.identicon { - line-height: 15px; - } - } - } - - &::after { - content: ""; - position: absolute; - height: 100%; - width: 100%; - background-color: transparent; - border: 2px outset $kdb-border; - border-radius: 50%; - animation: spin-avatar 3s infinite linear; - } - } - - .folder-toggle-wrap { - float: left; - line-height: $list-text-height; - font-size: 0; - - span { - font-size: $gl-font-size; - } - } - - .folder-caret, - .item-type-icon { - display: inline-block; - } - - .folder-caret { - width: 15px; - - svg { - margin-bottom: 2px; - } - } - - .item-type-icon { - margin-top: 2px; - width: 20px; - } - - > .group-row:not(.has-children) { - .folder-caret { - opacity: 0; - } - } - - .content-list li:last-child { - padding-bottom: 0; - } - - .group-list-tree { - margin-bottom: 0; - margin-left: 30px; - position: relative; - - &::before { - content: ''; - display: block; - width: 0; - position: absolute; - top: 5px; - bottom: 0; - left: -16px; - border-left: 2px solid $border-white-normal; - } - - .group-row { - position: relative; - - &::before { - content: ""; - display: block; - width: 10px; - height: 0; - border-top: 2px solid $border-white-normal; - position: absolute; - top: 30px; - left: -16px; - } - - &:last-child::before { - background: $white-light; - height: auto; - top: 30px; - bottom: 0; - } - - &.being-removed { - opacity: 0.5; - } - } - } - - .group-row { - padding: 0; - - &.has-children { - border-top: 0; - } - - &:first-child { - border-top: 1px solid $white-normal; - } - } - - .group-row-contents { - padding: $gl-padding-top; - - &:hover { - border-color: $row-hover-border; - background-color: $row-hover; - cursor: pointer; - } - - .avatar-container > a { - width: 100%; - text-decoration: none; - } - - &.has-more-items { - display: block; - padding: 20px 10px; - } - - .stats { - position: relative; - line-height: 46px; - - > span { - display: inline-flex; - align-items: center; - height: 16px; - min-width: 30px; - } - - > span:last-child { - margin-right: 0; - } - - .stat-value { - margin: 2px 0 0 5px; - } - } - - .controls { - margin-left: 5px; - - > .btn { - margin-right: $btn-xs-side-margin; - } - } - } - - .project-row-contents .stats { - line-height: inherit; - - > span:first-child { - margin-left: 25px; - } - - .item-visibility { - margin-right: 0; - } - - .last-updated { - position: absolute; - right: 12px; - min-width: 250px; - text-align: right; - color: $gl-text-color-secondary; - } - } -} - .namespace-title { .tooltip-inner { max-width: 350px; } } - -ul.group-list-tree { - li.group-row { - > .group-row-contents .title { - line-height: $list-text-height; - } - - &.has-description > .group-row-contents .title { - line-height: inherit; - } - } -} - -.js-groups-list-holder { - .groups-list-loading { - font-size: 34px; - text-align: center; - } -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index c378ad50836..409b7285f82 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -241,3 +241,231 @@ overflow-y: unset; } } + +.groups-list-tree-container { + .has-no-search-results { + text-align: center; + padding: $gl-padding; + font-style: italic; + color: $well-light-text-color; + } + + > .group-list-tree > .group-row.has-children:first-child { + border-top: 0; + } +} + +.group-list-tree { + .avatar-container.content-loading { + position: relative; + + > a, + > a .avatar { + height: 100%; + border-radius: 50%; + } + + > a { + padding: 2px; + + .avatar { + border: 2px solid $white-normal; + + &.identicon { + line-height: 15px; + } + } + } + + &::after { + content: ""; + position: absolute; + height: 100%; + width: 100%; + background-color: transparent; + border: 2px outset $kdb-border; + border-radius: 50%; + animation: spin-avatar 3s infinite linear; + } + } + + .folder-toggle-wrap { + float: left; + line-height: $list-text-height; + font-size: 0; + + span { + font-size: $gl-font-size; + } + } + + .folder-caret, + .item-type-icon { + display: inline-block; + } + + .folder-caret { + width: 15px; + + svg { + margin-bottom: 2px; + } + } + + .item-type-icon { + margin-top: 2px; + width: 20px; + } + + > .group-row:not(.has-children) { + .folder-caret { + opacity: 0; + } + } + + .content-list li:last-child { + padding-bottom: 0; + } + + .group-list-tree { + margin-bottom: 0; + margin-left: 30px; + position: relative; + + &::before { + content: ''; + display: block; + width: 0; + position: absolute; + top: 5px; + bottom: 0; + left: -16px; + border-left: 2px solid $border-white-normal; + } + + .group-row { + position: relative; + + &::before { + content: ""; + display: block; + width: 10px; + height: 0; + border-top: 2px solid $border-white-normal; + position: absolute; + top: 30px; + left: -16px; + } + + &:last-child::before { + background: $white-light; + height: auto; + top: 30px; + bottom: 0; + } + + &.being-removed { + opacity: 0.5; + } + } + } + + .group-row { + padding: 0; + + &.has-children { + border-top: 0; + } + + &:first-child { + border-top: 1px solid $white-normal; + } + } + + .group-row-contents { + padding: $gl-padding-top; + + &:hover { + border-color: $row-hover-border; + background-color: $row-hover; + cursor: pointer; + } + + .avatar-container > a { + width: 100%; + text-decoration: none; + } + + &.has-more-items { + display: block; + padding: 20px 10px; + } + + .stats { + position: relative; + line-height: 46px; + + > span { + display: inline-flex; + align-items: center; + height: 16px; + min-width: 30px; + } + + > span:last-child { + margin-right: 0; + } + + .stat-value { + margin: 2px 0 0 5px; + } + } + + .controls { + margin-left: 5px; + + > .btn { + margin-right: $btn-xs-side-margin; + } + } + } + + .project-row-contents .stats { + line-height: inherit; + + > span:first-child { + margin-left: 25px; + } + + .item-visibility { + margin-right: 0; + } + + .last-updated { + position: absolute; + right: 12px; + min-width: 250px; + text-align: right; + color: $gl-text-color-secondary; + } + } +} + +ul.group-list-tree { + li.group-row { + > .group-row-contents .title { + line-height: $list-text-height; + } + + &.has-description > .group-row-contents .title { + line-height: inherit; + } + } +} + +.js-groups-list-holder { + .groups-list-loading { + font-size: 34px; + text-align: center; + } +} -- cgit v1.2.3 From 6b98033d9120c7dfc5276f623e27e7af22dd7b88 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 17 May 2018 23:48:59 -0700 Subject: Fix api_json.log not always reporting the right HTTP status code As described in https://github.com/aserafin/grape_logging/issues/45, if a Grape error is caught by the handlers and a different return code is returned, then the api_json.log would have a 500 error code instead of the right value. Inserting the GrapeLogging middleware after the Grape middleware fixes this problem. Seen in https://gitlab.com/gitlab-com/infrastructure/issues/4249 --- .../unreleased/sh-fix-grape-logging-status-code.yml | 5 +++++ lib/api/api.rb | 17 +++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/sh-fix-grape-logging-status-code.yml diff --git a/changelogs/unreleased/sh-fix-grape-logging-status-code.yml b/changelogs/unreleased/sh-fix-grape-logging-status-code.yml new file mode 100644 index 00000000000..aabf9a84bfb --- /dev/null +++ b/changelogs/unreleased/sh-fix-grape-logging-status-code.yml @@ -0,0 +1,5 @@ +--- +title: Fix api_json.log not always reporting the right HTTP status code +merge_request: +author: +type: fixed diff --git a/lib/api/api.rb b/lib/api/api.rb index 5139e869c71..2fbeaaffcfe 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -8,14 +8,15 @@ module API PROJECT_ENDPOINT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze COMMIT_ENDPOINT_REQUIREMENTS = PROJECT_ENDPOINT_REQUIREMENTS.merge(sha: NO_SLASH_URL_PART_REGEX).freeze - use GrapeLogging::Middleware::RequestLogger, - logger: Logger.new(LOG_FILENAME), - formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new, - include: [ - GrapeLogging::Loggers::FilterParameters.new, - GrapeLogging::Loggers::ClientEnv.new, - Gitlab::GrapeLogging::Loggers::UserLogger.new - ] + insert_before Grape::Middleware::Error, + GrapeLogging::Middleware::RequestLogger, + logger: Logger.new(LOG_FILENAME), + formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new, + include: [ + GrapeLogging::Loggers::FilterParameters.new, + GrapeLogging::Loggers::ClientEnv.new, + Gitlab::GrapeLogging::Loggers::UserLogger.new + ] allow_access_with_scope :api prefix :api -- cgit v1.2.3 From af9b0bfbae84a402e5c706ac29772b0d70dfa156 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 18 May 2018 10:14:10 +0200 Subject: Simplify untrusted regexp factory method --- lib/gitlab/untrusted_regexp.rb | 15 ++++++--------- .../gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb | 4 ++-- spec/lib/gitlab/untrusted_regexp_spec.rb | 16 ++++++---------- .../shared_examples/malicious_regexp_shared_examples.rb | 2 ++ 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb index 70d1a7c6535..dc2d91dfa23 100644 --- a/lib/gitlab/untrusted_regexp.rb +++ b/lib/gitlab/untrusted_regexp.rb @@ -55,7 +55,7 @@ module Gitlab end def self.valid?(pattern) - self.fabricate(pattern) + !!self.fabricate(pattern) rescue RegexpError false end @@ -63,16 +63,13 @@ module Gitlab def self.fabricate(pattern) matches = pattern.match(%r{^/(?.+)/(?[ismU]*)$}) - if matches - expression = matches[:regexp] - flags = matches[:flags] + raise RegexpError, 'Invalid regular expression!' if matches.nil? - expression.prepend("(?#{flags})") if flags.present? + expression = matches[:regexp] + flags = matches[:flags] + expression.prepend("(?#{flags})") if flags.present? - self.new(expression, multiline: false) - else - self.new(pattern, multiline: false) - end + self.new(expression, multiline: false) end private diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb index c63c38b1dbc..3ebc2e94727 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb @@ -79,7 +79,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do describe '#evaluate' do it 'returns a regular expression' do - regexp = described_class.new('abc') + regexp = described_class.new('/abc/') expect(regexp.evaluate).to eq Gitlab::UntrustedRegexp.new('abc') end @@ -87,7 +87,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do it 'raises error if evaluated regexp is not valid' do allow(Gitlab::UntrustedRegexp).to receive(:valid?).and_return(true) - regexp = described_class.new('invalid ( .*') + regexp = described_class.new('/invalid ( .*/') expect { regexp.evaluate } .to raise_error(Gitlab::Ci::Pipeline::Expression::RuntimeError) diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb index 4bca320ac2c..0a6ac0aa294 100644 --- a/spec/lib/gitlab/untrusted_regexp_spec.rb +++ b/spec/lib/gitlab/untrusted_regexp_spec.rb @@ -4,9 +4,13 @@ require 'support/shared_examples/malicious_regexp_shared_examples' describe Gitlab::UntrustedRegexp do describe '.valid?' do it 'returns true if regexp is valid' do + expect(described_class.valid?('/some ( thing/')) + .to be false end it 'returns true if regexp is invalid' do + expect(described_class.valid?('/some .* thing/')) + .to be true end end @@ -32,17 +36,9 @@ describe Gitlab::UntrustedRegexp do end end - context 'when regexp is not plain pattern' do - it 'fabricates regexp without flags' do - regexp = described_class.fabricate('something') - - expect(regexp).to eq described_class.new('something') - end - end - - context 'when regexp is invalid' do + context 'when regexp is a raw pattern' do it 'raises an error' do - expect { described_class.fabricate('/some ( thing/') } + expect { described_class.fabricate('some .* thing') } .to raise_error(RegexpError) end end diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb index ac5d22298bb..65026f1d7c0 100644 --- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb +++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb @@ -1,3 +1,5 @@ +require 'timeout' + shared_examples 'malicious regexp' do let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' } let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' } -- cgit v1.2.3 From afa245142117a7e90ff6046133a2402fb8c09cb1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 18 May 2018 10:14:19 +0200 Subject: Simplify pattern lexeme fabrication and matcher --- lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb index 70a221010f3..9b239c29ea4 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb @@ -6,7 +6,7 @@ module Gitlab require_dependency 're2' class Pattern < Lexeme::Value - PATTERN = %r{^(?/.+/[ismU]*)$}.freeze + PATTERN = %r{^/.+/[ismU]*$}.freeze def initialize(regexp) @value = regexp @@ -23,7 +23,7 @@ module Gitlab end def self.build(string) - new(string.match(PATTERN)[:regexp]) + new(string) end end end -- cgit v1.2.3 From 7feef84e1e60bef5cd460345125cc83dd23350cc Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Fri, 18 May 2018 08:39:42 +0000 Subject: Make stores export a createStore() which can be used in tests --- doc/development/fe_guide/vuex.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index 8997a5889dc..858b03c60bf 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -37,12 +37,13 @@ import state from './state'; Vue.use(Vuex); -export default new Vuex.Store({ +export const createStore = () => new Vuex.Store({ actions, getters, mutations, state, }); +export default createStore(); ``` ### `state.js` @@ -320,10 +321,11 @@ In order to write unit tests for those components, we need to include the store ```javascript //component_spec.js import Vue from 'vue'; -import store from './store'; +import { createStore } from './store'; import component from './component.vue' describe('component', () => { + let store; let vm; let Component; @@ -340,6 +342,8 @@ describe('component', () => { name: 'Foo', age: '30', }; + + store = createStore(); // populate the store store.dispatch('addUser', user); -- cgit v1.2.3 From 9f7deb85b5d6937e6cf7068b864f49693b4a2623 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 18 May 2018 11:28:32 +0200 Subject: Do not allow to use `CI_PIPELINE_ID` in environment name --- app/models/ci/pipeline.rb | 9 +++++++-- spec/models/ci/pipeline_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index c26f0b6dcdc..7d7349b04bc 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -523,9 +523,14 @@ module Ci strong_memoize(:legacy_trigger) { trigger_requests.first } end + def persisted_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.append(key: 'CI_PIPELINE_ID', value: id.to_s) if persisted? + end + end + def predefined_variables - Gitlab::Ci::Variables::Collection.new - .append(key: 'CI_PIPELINE_ID', value: id.to_s) + persisted_variables .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) .append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e7845b693a1..0e393aa362b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -167,6 +167,28 @@ describe Ci::Pipeline, :mailer do end end + describe '#persisted_variables' do + context 'when pipeline is not persisted yet' do + subject { build(:ci_pipeline).persisted_variables } + + it 'does not contain some variables' do + keys = subject.map { |variable| variable[:key] } + + expect(keys).not_to include 'CI_PIPELINE_ID' + end + end + + context 'when pipeline is persisted' do + subject { build_stubbed(:ci_pipeline).persisted_variables } + + it 'does not contain some variables' do + keys = subject.map { |variable| variable[:key] } + + expect(keys).to include 'CI_PIPELINE_ID' + end + end + end + describe '#predefined_variables' do subject { pipeline.predefined_variables } -- cgit v1.2.3 From 1f2c56dda538b11f95a6ec0b16a57a46c4182113 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Fri, 18 May 2018 10:38:43 +0100 Subject: Update EE > CE downgrade service removal steps --- doc/downgrade_ee_to_ce/README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md index f656057e3da..236408762e3 100644 --- a/doc/downgrade_ee_to_ce/README.md +++ b/doc/downgrade_ee_to_ce/README.md @@ -15,9 +15,9 @@ Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so you should disable these mechanisms before downgrading and you should provide alternative authentication methods to your users. -### Remove Jenkins CI Service entries from the database +### Remove Service Integration entries from the database -The `JenkinsService` class is only available on the Enterprise Edition codebase, +The `JenkinsService` and `GithubService` classes are only available in the Enterprise Edition codebase, so if you downgrade to the Community Edition, you'll come across the following error: @@ -30,20 +30,31 @@ column if you didn't intend it to be used for storing the inheritance class or o use another column for that information.) ``` +or + +``` +Completed 500 Internal Server Error in 497ms (ActiveRecord: 32.2ms) + +ActionView::Template::Error (The single-table inheritance mechanism failed to locate the subclass: 'GithubService'. This +error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this +column if you didn't intend it to be used for storing the inheritance class or overwrite Service.inheritance_column to +use another column for that information.) +``` + All services are created automatically for every project you have, so in order to avoid getting this error, you need to remove all instances of the -`JenkinsService` from your database: +`JenkinsService` and `GithubService` from your database: **Omnibus Installation** ``` -$ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" +$ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService', 'GithubService']).delete_all" ``` **Source Installation** ``` -$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production +$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService', 'GithubService']).delete_all" production ``` ### Secret variables environment scopes -- cgit v1.2.3 From 51214bf0e179e3c41d373595397b2a2d10fcb379 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 15 May 2018 19:31:35 +0200 Subject: Add docs for incremental rollouts --- doc/topics/autodevops/img/rollout_enabled.png | Bin 0 -> 19536 bytes .../autodevops/img/rollout_staging_disabled.png | Bin 0 -> 13837 bytes .../autodevops/img/rollout_staging_enabled.png | Bin 0 -> 17306 bytes doc/topics/autodevops/img/staging_enabled.png | Bin 0 -> 17929 bytes doc/topics/autodevops/index.md | 52 +++++++++++++++++++++ 5 files changed, 52 insertions(+) create mode 100644 doc/topics/autodevops/img/rollout_enabled.png create mode 100644 doc/topics/autodevops/img/rollout_staging_disabled.png create mode 100644 doc/topics/autodevops/img/rollout_staging_enabled.png create mode 100644 doc/topics/autodevops/img/staging_enabled.png diff --git a/doc/topics/autodevops/img/rollout_enabled.png b/doc/topics/autodevops/img/rollout_enabled.png new file mode 100644 index 00000000000..d6e7d790cf2 Binary files /dev/null and b/doc/topics/autodevops/img/rollout_enabled.png differ diff --git a/doc/topics/autodevops/img/rollout_staging_disabled.png b/doc/topics/autodevops/img/rollout_staging_disabled.png new file mode 100644 index 00000000000..71e36b440f0 Binary files /dev/null and b/doc/topics/autodevops/img/rollout_staging_disabled.png differ diff --git a/doc/topics/autodevops/img/rollout_staging_enabled.png b/doc/topics/autodevops/img/rollout_staging_enabled.png new file mode 100644 index 00000000000..d0d1d356627 Binary files /dev/null and b/doc/topics/autodevops/img/rollout_staging_enabled.png differ diff --git a/doc/topics/autodevops/img/staging_enabled.png b/doc/topics/autodevops/img/staging_enabled.png new file mode 100644 index 00000000000..0ef1a67d641 Binary files /dev/null and b/doc/topics/autodevops/img/staging_enabled.png differ diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 5254e6e3d9a..33e2d710410 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -496,6 +496,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. | | `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | +| `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. | TIP: **Tip:** Set up the replica variables using a @@ -578,6 +579,57 @@ If `STAGING_ENABLED` is defined in your project (e.g., set `STAGING_ENABLED` to to a `staging` environment, and a `production_manual` job will be created for you when you're ready to manually deploy to production. +#### Incremental rollout to production **[PREMIUM]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5415) in GitLab 10.8. + +When you have a new version of your app to deploy in production, you may want +to use an incremental rollout to replace just a few pods with the latest code. +This will allow you to first check how the app is behaving, and later manually +increasing the rollout up to 100%. + +If `INCREMENTAL_ROLLOUT_ENABLED` is defined in your project (e.g., set +`INCREMENTAL_ROLLOUT_ENABLED` to `1` as a secret variable), then instead of the +standard `production` job, 4 different +[manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph) +will be created: + +1. `rollout 10%` +1. `rollout 25%` +1. `rollout 50%` +1. `rollout 100%` + +The percentage is based on the `REPLICAS` variable and defines the number of +pods you want to have for your deployment. If you say `10`, and then you run +the `10%` rollout job, there will be `1` new pod + `9` old ones. + +To start a job, click on the play icon next to the job's name. You are not +required to go from `10%` to `100%`, you can jump to whatever job you want. +You can also scale down by running a lower percentage job, just before hitting +`100%`. Once you get to `100%`, you cannot scale down, and you'd have to roll +back by redeploying the old version using the +[rollback button](../../ci/environments.md#rolling-back-changes) in the +environment page. + +Below, you can see how the pipeline will look if the rollout or staging +variables are defined. + +- **Without `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** + + ![Staging and rollout disabled](img/rollout_staging_disabled.png) + +- **Without `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** + + ![Staging enabled](img/staging_enabled.png) + +- **With `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** + + ![Rollout enabled](img/rollout_enabled.png) + +- **With `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** + + ![Rollout and staging enabled](img/rollout_staging_enabled.png) + ## Currently supported languages NOTE: **Note:** -- cgit v1.2.3 From 2ad01c5ab079959205c236df7bf8cbb5c1fab573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 19 Apr 2018 18:53:07 +0200 Subject: Ensure Flipper memoizer is used in Sidekiq's context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, don't use the provided Middleware, which isn't thread-safe, and instantiate a new Flipper instance per thread instead. Signed-off-by: Rémy Coutable --- config/initializers/flipper.rb | 21 --------------------- lib/feature.rb | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index c60ad535fd5..80cab7273e5 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -1,22 +1 @@ -require 'flipper/adapters/active_record' -require 'flipper/adapters/active_support_cache_store' - -Flipper.configure do |config| - config.default do - adapter = Flipper::Adapters::ActiveRecord.new( - feature_class: Feature::FlipperFeature, gate_class: Feature::FlipperGate) - cached_adapter = Flipper::Adapters::ActiveSupportCacheStore.new( - adapter, - Rails.cache, - expires_in: 1.hour) - - Flipper.new(cached_adapter) - end -end - Feature.register_feature_groups - -unless Rails.env.test? - require 'flipper/middleware/memoizer' - Rails.application.config.middleware.use Flipper::Middleware::Memoizer -end diff --git a/lib/feature.rb b/lib/feature.rb index 8e9ba5c530a..6474de6e56d 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -1,3 +1,6 @@ +require 'flipper/adapters/active_record' +require 'flipper/adapters/active_support_cache_store' + class Feature # Classes to override flipper table names class FlipperFeature < Flipper::Adapters::ActiveRecord::Feature @@ -60,7 +63,8 @@ class Feature end def flipper - @flipper ||= Flipper.instance + Thread.current[:flipper] ||= + Flipper.new(flipper_adapter).tap { |flip| flip.memoize = true } end # This method is called from config/initializers/flipper.rb and can be used @@ -68,5 +72,16 @@ class Feature # See https://docs.gitlab.com/ee/development/feature_flags.html#feature-groups def register_feature_groups end + + def flipper_adapter + active_record_adapter = Flipper::Adapters::ActiveRecord.new( + feature_class: FlipperFeature, + gate_class: FlipperGate) + + Flipper::Adapters::ActiveSupportCacheStore.new( + active_record_adapter, + Rails.cache, + expires_in: 1.hour) + end end end -- cgit v1.2.3 From c0e77f7c9cc24104981bb8f6973ceeb9c311e1e2 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 18 May 2018 10:25:59 +0000 Subject: Resolve "Expand API: Render an arbitrary Markdown document" --- ...d-api-render-an-arbitrary-markdown-document.yml | 5 + doc/api/README.md | 1 + doc/api/markdown.md | 29 ++++++ lib/api/api.rb | 1 + lib/api/markdown.rb | 33 ++++++ lib/banzai/filter/reference_filter.rb | 2 +- lib/banzai/pipeline/gfm_pipeline.rb | 4 +- spec/requests/api/markdown_spec.rb | 112 +++++++++++++++++++++ 8 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/19861-expand-api-render-an-arbitrary-markdown-document.yml create mode 100644 doc/api/markdown.md create mode 100644 lib/api/markdown.rb create mode 100644 spec/requests/api/markdown_spec.rb diff --git a/changelogs/unreleased/19861-expand-api-render-an-arbitrary-markdown-document.yml b/changelogs/unreleased/19861-expand-api-render-an-arbitrary-markdown-document.yml new file mode 100644 index 00000000000..a97e8a2b5cc --- /dev/null +++ b/changelogs/unreleased/19861-expand-api-render-an-arbitrary-markdown-document.yml @@ -0,0 +1,5 @@ +--- +title: Add API endpoint to render markdown text +merge_request: 18926 +author: "@blackst0ne" +type: added diff --git a/doc/api/README.md b/doc/api/README.md index e777fc63d2b..194907accc7 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -33,6 +33,7 @@ following locations: - [Jobs](jobs.md) - [Keys](keys.md) - [Labels](labels.md) +- [Markdown](markdown.md) - [Merge Requests](merge_requests.md) - [Project milestones](milestones.md) - [Group milestones](group_milestones.md) diff --git a/doc/api/markdown.md b/doc/api/markdown.md new file mode 100644 index 00000000000..f406838e887 --- /dev/null +++ b/doc/api/markdown.md @@ -0,0 +1,29 @@ +# Markdown API + +> [Introduced][ce-18926] in GitLab 11.0. + +Available only in APIv4. + +## Render an arbitrary Markdown document + +``` +POST /api/v4/markdown +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | ------------- | ------------------------------------------ | +| `text` | string | yes | The markdown text to render | +| `gfm` | boolean | no (optional) | Render text using GitLab Flavored Markdown. Default is `false` | +| `project` | string | no (optional) | Use `project` as a context when creating references using GitLab Flavored Markdown. [Authentication](README.html#authentication) is required if a project is not public. | + +```bash +curl --header Content-Type:application/json --data '{"text":"Hello world! :tada:", "gfm":true, "project":"group_example/project_example"}' https://gitlab.example.com/api/v4/markdown +``` + +Response example: + +```json +{ "html": "

Hello world! 🎉

" } +``` + +[ce-18926]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18926 diff --git a/lib/api/api.rb b/lib/api/api.rb index 2fbeaaffcfe..de20b2b8e67 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -140,6 +140,7 @@ module API mount ::API::Keys mount ::API::Labels mount ::API::Lint + mount ::API::Markdown mount ::API::Members mount ::API::MergeRequestDiffs mount ::API::MergeRequests diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb new file mode 100644 index 00000000000..b9ed68aa584 --- /dev/null +++ b/lib/api/markdown.rb @@ -0,0 +1,33 @@ +module API + class Markdown < Grape::API + params do + requires :text, type: String, desc: "The markdown text to render" + optional :gfm, type: Boolean, desc: "Render text using GitLab Flavored Markdown" + optional :project, type: String, desc: "The full path of a project to use as the context when creating references using GitLab Flavored Markdown" + end + resource :markdown do + desc "Render markdown text" do + detail "This feature was introduced in GitLab 11.0." + end + post do + # Explicitly set CommonMark as markdown engine to use. + # Remove this set when https://gitlab.com/gitlab-org/gitlab-ce/issues/43011 is done. + context = { markdown_engine: :common_mark, only_path: false } + + if params[:project] + project = Project.find_by_full_path(params[:project]) + + not_found!("Project") unless can?(current_user, :read_project, project) + + context[:project] = project + else + context[:skip_project_check] = true + end + + context[:pipeline] = params[:gfm] ? :full : :plain_markdown + + { html: Banzai.render(params[:text], context) } + end + end + end +end diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index b9d5ecf70ec..2f023f4f242 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -73,7 +73,7 @@ module Banzai # # Note that while the key might exist, its value could be nil! def validate - needs :project + needs :project unless skip_project_check? end # Iterates over all and text() nodes in a document. diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 8b2f05fffec..a1f24e8b093 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -42,9 +42,9 @@ module Banzai end def self.transform_context(context) - context.merge( - only_path: true, + context[:only_path] = true unless context.key?(:only_path) + context.merge( # EmojiFilter asset_host: Gitlab::Application.config.asset_host, asset_root: Gitlab.config.gitlab.base_url diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb new file mode 100644 index 00000000000..a55796cf343 --- /dev/null +++ b/spec/requests/api/markdown_spec.rb @@ -0,0 +1,112 @@ +require "spec_helper" + +describe API::Markdown do + RSpec::Matchers.define_negated_matcher :exclude, :include + + describe "POST /markdown" do + let(:user) {} # No-op. It gets overwritten in the contexts below. + + before do + post api("/markdown", user), params + end + + shared_examples "rendered markdown text without GFM" do + it "renders markdown text" do + expect(response).to have_http_status(201) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["html"]).to eq("

#{text}

") + end + end + + shared_examples "404 Project Not Found" do + it "responses with 404 Not Found" do + expect(response).to have_http_status(404) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["message"]).to eq("404 Project Not Found") + end + end + + context "when arguments are invalid" do + context "when text is missing" do + let(:params) { {} } + + it "responses with 400 Bad Request" do + expect(response).to have_http_status(400) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["error"]).to eq("text is missing") + end + end + + context "when project is not found" do + let(:params) { { text: "Hello world!", gfm: true, project: "Dummy project" } } + + it_behaves_like "404 Project Not Found" + end + end + + context "when arguments are valid" do + set(:project) { create(:project) } + set(:issue) { create(:issue, project: project) } + let(:text) { ":tada: Hello world! :100: #{issue.to_reference}" } + + context "when not using gfm" do + context "without project" do + let(:params) { { text: text } } + + it_behaves_like "rendered markdown text without GFM" + end + + context "with project" do + let(:params) { { text: text, project: project.full_path } } + + context "when not authorized" do + it_behaves_like "404 Project Not Found" + end + + context "when authorized" do + let(:user) { project.owner } + + it_behaves_like "rendered markdown text without GFM" + end + end + end + + context "when using gfm" do + context "without project" do + let(:params) { { text: text, gfm: true } } + + it "renders markdown text" do + expect(response).to have_http_status(201) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["html"]).to include("Hello world!") + .and include('data-name="tada"') + .and include('data-name="100"') + .and include("#1") + .and exclude("
") + end + end + + context "with project" do + let(:params) { { text: text, gfm: true, project: project.full_path } } + let(:user) { project.owner } + + it "renders markdown text" do + expect(response).to have_http_status(201) + expect(response.headers["Content-Type"]).to eq("application/json") + expect(json_response).to be_a(Hash) + expect(json_response["html"]).to include("Hello world!") + .and include('data-name="tada"') + .and include('data-name="100"') + .and include("") + end + end + end + end + end +end -- cgit v1.2.3 From 61a5994db6f8b1e98f7ee1460f8aadb2ddb6c713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20=22BKC=22=20Carlb=C3=A4cker?= Date: Fri, 4 May 2018 15:53:01 +0200 Subject: Migrate RepositoryService#SearchFilesBy{Content,Name} --- GITALY_SERVER_VERSION | 2 +- lib/gitlab/git/repository.rb | 26 +++++++++++++++++++++----- lib/gitlab/gitaly_client/repository_service.rb | 10 ++++++++++ spec/models/repository_spec.rb | 14 ++++++++++++-- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 897e21587ed..7bb21aff834 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.100.0 +0.102.0 diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 061865a7acf..f043df7eba5 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1473,10 +1473,19 @@ module Gitlab def search_files_by_content(query, ref) return [] if empty? || query.blank? - offset = 2 - args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) + safe_query = Regexp.escape(query) + ref ||= root_ref + + gitaly_migrate(:search_files_by_content) do |is_enabled| + if is_enabled + gitaly_repository_client.search_files_by_content(ref, safe_query) + else + offset = 2 + args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{safe_query} #{ref}) - run_git(args).first.scrub.split(/^--\n/) + run_git(args).first.scrub.split(/^--\n/) + end + end end def can_be_merged?(source_sha, target_branch) @@ -1491,12 +1500,19 @@ module Gitlab def search_files_by_name(query, ref) safe_query = Regexp.escape(query.sub(%r{^/*}, "")) + ref ||= root_ref return [] if empty? || safe_query.blank? - args = %W(ls-tree -r --name-status --full-tree #{ref || root_ref} -- #{safe_query}) + gitaly_migrate(:search_files_by_name) do |is_enabled| + if is_enabled + gitaly_repository_client.search_files_by_name(ref, safe_query) + else + args = %W(ls-tree -r --name-status --full-tree #{ref} -- #{safe_query}) - run_git(args).first.lines.map(&:strip) + run_git(args).first.lines.map(&:strip) + end + end end def find_commits_by_message(query, ref, path, limit, offset) diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 132a5947f17..ee01f5a5bd9 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -301,6 +301,16 @@ module Gitlab GitalyClient.call(@storage, :repository_service, :get_raw_changes, request) end + + def search_files_by_name(ref, query) + request = Gitaly::SearchFilesByNameRequest.new(repository: @gitaly_repo, ref: ref, query: query) + GitalyClient.call(@storage, :repository_service, :search_files_by_name, request).flat_map(&:files) + end + + def search_files_by_content(ref, query) + request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query) + GitalyClient.call(@storage, :repository_service, :search_files_by_content, request).flat_map(&:matches) + end end end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index ac8d9a32d4e..6bc148a1392 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -671,7 +671,7 @@ describe Repository do end end - describe "search_files_by_content" do + shared_examples "search_files_by_content" do let(:results) { repository.search_files_by_content('feature', 'master') } subject { results } @@ -718,7 +718,7 @@ describe Repository do end end - describe "search_files_by_name" do + shared_examples "search_files_by_name" do let(:results) { repository.search_files_by_name('files', 'master') } it 'returns result' do @@ -758,6 +758,16 @@ describe Repository do end end + describe 'with gitaly enabled' do + it_behaves_like 'search_files_by_content' + it_behaves_like 'search_files_by_name' + end + + describe 'with gitaly disabled', :disable_gitaly do + it_behaves_like 'search_files_by_content' + it_behaves_like 'search_files_by_name' + end + describe '#async_remove_remote' do before do masterrev = repository.find_branch('master').dereferenced_target -- cgit v1.2.3 From d9a3f020be570f135c0ce7c7676b4c1ed332ce1d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 18 May 2018 14:05:29 +0200 Subject: Separate persisted and runtime pipeline variables --- app/models/ci/build.rb | 1 + app/models/ci/pipeline.rb | 2 +- spec/models/ci/build_spec.rb | 8 ++++++++ spec/services/ci/create_pipeline_service_spec.rb | 21 ++++++++++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61c10c427dd..78f054a6527 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -599,6 +599,7 @@ module Ci break variables unless persisted? variables + .concat(pipeline.persisted_variables) .append(key: 'CI_JOB_ID', value: id.to_s) .append(key: 'CI_JOB_TOKEN', value: token, public: false) .append(key: 'CI_BUILD_ID', value: id.to_s) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 7d7349b04bc..53af87a271a 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -530,7 +530,7 @@ module Ci end def predefined_variables - persisted_variables + Gitlab::Ci::Variables::Collection.new .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) .append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index dc810489011..d7659a9cb58 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -629,6 +629,14 @@ describe Ci::Build do it { is_expected.to eq('review/host') } end + + context 'when using persisted variables' do + let(:build) do + create(:ci_build, environment: 'review/x$CI_BUILD_ID') + end + + it { is_expected.to eq('review/x') } + end end describe '#starts_environment?' do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 9a0b6efd8a9..be0255a2ccc 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -395,7 +395,26 @@ describe Ci::CreatePipelineService do result = execute_service expect(result).to be_persisted - expect(Environment.find_by(name: "review/master")).not_to be_nil + expect(Environment.find_by(name: "review/master")).to be_present + end + end + + context 'with environment name including persisted variables' do + before do + config = YAML.dump( + deploy: { + environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_BUILD_ID" }, + script: 'ls' } + ) + + stub_ci_pipeline_yaml_file(config) + end + + it 'skipps persisted variables in environment name' do + result = execute_service + + expect(result).to be_persisted + expect(Environment.find_by(name: "review/id1/id2")).to be_present end end -- cgit v1.2.3 From 5d462e9d9893e7f27d45964eeca0f045e651c2a7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 18 May 2018 14:12:46 +0200 Subject: Update docs about unsupported environment name variables --- doc/ci/environments.md | 1 + doc/ci/variables/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 517e25f00f7..3a491f0073c 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -252,6 +252,7 @@ including predefined, secure variables and `.gitlab-ci.yml` [`variables`](yaml/README.md#variables). You however cannot use variables defined under `script` or on the Runner's side. There are other variables that are unsupported in environment name context: +- `CI_PIPELINE_ID` - `CI_JOB_ID` - `CI_JOB_TOKEN` - `CI_BUILD_ID` diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 42367bf13f7..7e795ba973b 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -543,6 +543,7 @@ We do not support variables containing tokens because of security reasons. You can find a full list of unsupported variables below: +- `CI_PIPELINE_ID` - `CI_JOB_ID` - `CI_JOB_TOKEN` - `CI_BUILD_ID` -- cgit v1.2.3 From eb63895f04e85224bd8616588d6fb95da459a614 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 18 May 2018 14:14:08 +0200 Subject: Add changelog log entry for persisted variables fix --- .../fix-gb-exclude-persisted-variables-from-environment-name.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/fix-gb-exclude-persisted-variables-from-environment-name.yml diff --git a/changelogs/unreleased/fix-gb-exclude-persisted-variables-from-environment-name.yml b/changelogs/unreleased/fix-gb-exclude-persisted-variables-from-environment-name.yml new file mode 100644 index 00000000000..92426832f30 --- /dev/null +++ b/changelogs/unreleased/fix-gb-exclude-persisted-variables-from-environment-name.yml @@ -0,0 +1,5 @@ +--- +title: Exclude CI_PIPELINE_ID from variables supported in dynamic environment name +merge_request: 19032 +author: +type: fixed -- cgit v1.2.3 From b5d7937e42126040733fead66f8bc3fabfb5fbe2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 18 May 2018 14:19:57 +0200 Subject: Update pipeline persisted / predefined variables specs --- spec/models/ci/build_spec.rb | 2 +- spec/models/ci/pipeline_spec.rb | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index d7659a9cb58..884fbad6ccf 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1493,6 +1493,7 @@ describe Ci::Build do let(:container_registry_enabled) { false } let(:predefined_variables) do [ + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_JOB_ID', value: build.id.to_s, public: true }, { key: 'CI_JOB_TOKEN', value: build.token, public: false }, { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, @@ -1524,7 +1525,6 @@ describe Ci::Build do { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }, { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true }, diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 0e393aa362b..e4f4c62bd22 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -181,10 +181,10 @@ describe Ci::Pipeline, :mailer do context 'when pipeline is persisted' do subject { build_stubbed(:ci_pipeline).persisted_variables } - it 'does not contain some variables' do + it 'does contains persisted variables' do keys = subject.map { |variable| variable[:key] } - expect(keys).to include 'CI_PIPELINE_ID' + expect(keys).to eq %w[CI_PIPELINE_ID] end end end @@ -195,7 +195,11 @@ describe Ci::Pipeline, :mailer do it 'includes all predefined variables in a valid order' do keys = subject.map { |variable| variable[:key] } - expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION] + expect(keys).to eq %w[CI_CONFIG_PATH + CI_PIPELINE_SOURCE + CI_COMMIT_MESSAGE + CI_COMMIT_TITLE + CI_COMMIT_DESCRIPTION] end end -- cgit v1.2.3 From 2ebafdfb2f026580153fd2cf50f4b6b7ab3a0344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 18 May 2018 15:33:54 +0200 Subject: Improve cacheable module --- app/models/concerns/redis_cacheable.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index b5425295130..53f022e2b35 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -7,11 +7,11 @@ module RedisCacheable class_methods do def cached_attr_reader(*attributes) attributes.each do |attribute| - define_method(attribute) do - unless self.has_attribute?(attribute) - raise ArgumentError, "`cached_attr_reader` requires the #{self.class.name}\##{attribute} attribute to have a database column" - end + unless self.column_names.include?(attribute.to_s) + raise ArgumentError, "`cached_attr_reader` requires the #{self.name}##{attribute} to be a database attribute" + end + define_method(attribute) do cached_attribute(attribute) || read_attribute(attribute) end end @@ -50,7 +50,9 @@ module RedisCacheable if Gitlab.rails5? self.class.type_for_attribute(attribute).cast(value) else - self.class.column_for_attribute(attribute).type_cast_from_database(value) + ActiveSupport::Deprecation.silence do + self.class.column_for_attribute(attribute).type_cast_from_database(value) + end end end end -- cgit v1.2.3 From 18a8eb96b34db63101c2b1210c1f93342b138a55 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 11 May 2018 16:44:38 +0200 Subject: Calculating repository checksums executed by Gitaly OPT_OUT status has been removed, and alternative implementation removed. Also checks if the repository exists before executing the checksum RPC to guard against NotFound errors. Closes gitlab-org/gitaly#1105 --- .../unreleased/zj-calculate-checksum-mandator.yml | 5 ++ lib/gitlab/git/repository.rb | 44 ++----------- spec/lib/gitlab/git/repository_spec.rb | 72 ++++++++-------------- 3 files changed, 35 insertions(+), 86 deletions(-) create mode 100644 changelogs/unreleased/zj-calculate-checksum-mandator.yml diff --git a/changelogs/unreleased/zj-calculate-checksum-mandator.yml b/changelogs/unreleased/zj-calculate-checksum-mandator.yml new file mode 100644 index 00000000000..83315a3c5dd --- /dev/null +++ b/changelogs/unreleased/zj-calculate-checksum-mandator.yml @@ -0,0 +1,5 @@ +--- +title: Remove shellout implementation for Repository checksums +merge_request: +author: +type: other diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 25487f53999..f612d9dc597 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1576,14 +1576,12 @@ module Gitlab end def checksum - gitaly_migrate(:calculate_checksum, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_repository_client.calculate_checksum - else - calculate_checksum_by_shelling_out - end - end + # The exists? RPC is much cheaper, so we perform this request first + raise NoRepository, "Repository does not exists" unless exists? + + gitaly_repository_client.calculate_checksum + rescue GRPC::NotFound + raise NoRepository # Guard against data races. end private @@ -2498,36 +2496,6 @@ module Gitlab rev_parse_target(ref).oid end - def calculate_checksum_by_shelling_out - raise NoRepository unless exists? - - args = %W(--git-dir=#{path} show-ref --heads --tags) - output, status = run_git(args) - - if status.nil? || !status.zero? - # Non-valid git repositories return 128 as the status code and an error output - raise InvalidRepository if status == 128 && output.to_s.downcase =~ /not a git repository/ - # Empty repositories returns with a non-zero status and an empty output. - raise ChecksumError, output unless output.blank? - - return EMPTY_REPOSITORY_CHECKSUM - end - - refs = output.split("\n") - - result = refs.inject(nil) do |checksum, ref| - value = Digest::SHA1.hexdigest(ref).hex - - if checksum.nil? - value - else - checksum ^ value - end - end - - result.to_s(16) - end - def build_git_cmd(*args) object_directories = alternate_object_directories.join(File::PATH_SEPARATOR) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index fcb690d8aa3..2b5710ac401 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2247,66 +2247,42 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#checksum' do - shared_examples 'calculating checksum' do - it 'calculates the checksum for non-empty repo' do - expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4' - end - - it 'returns 0000000000000000000000000000000000000000 for an empty repo' do - FileUtils.rm_rf(File.join(storage_path, 'empty-repo.git')) - - system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git), - chdir: storage_path, - out: '/dev/null', - err: '/dev/null') + it 'calculates the checksum for non-empty repo' do + expect(repository.checksum).to eq '54f21be4c32c02f6788d72207fa03ad3bce725e4' + end - empty_repo = described_class.new('default', 'empty-repo.git', '') + it 'returns 0000000000000000000000000000000000000000 for an empty repo' do + FileUtils.rm_rf(File.join(storage_path, 'empty-repo.git')) - expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000' - end + system(git_env, *%W(#{Gitlab.config.git.bin_path} init --bare empty-repo.git), + chdir: storage_path, + out: '/dev/null', + err: '/dev/null') - it 'raises Gitlab::Git::Repository::InvalidRepository error for non-valid git repo' do - FileUtils.rm_rf(File.join(storage_path, 'non-valid.git')) + empty_repo = described_class.new('default', 'empty-repo.git', '') - system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} non-valid.git), - chdir: SEED_STORAGE_PATH, - out: '/dev/null', - err: '/dev/null') + expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000' + end - File.truncate(File.join(storage_path, 'non-valid.git/HEAD'), 0) + it 'raises Gitlab::Git::Repository::InvalidRepository error for non-valid git repo' do + FileUtils.rm_rf(File.join(storage_path, 'non-valid.git')) - non_valid = described_class.new('default', 'non-valid.git', '') + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} non-valid.git), + chdir: SEED_STORAGE_PATH, + out: '/dev/null', + err: '/dev/null') - expect { non_valid.checksum }.to raise_error(Gitlab::Git::Repository::InvalidRepository) - end + File.truncate(File.join(storage_path, 'non-valid.git/HEAD'), 0) - it 'raises Gitlab::Git::Repository::NoRepository error when there is no repo' do - broken_repo = described_class.new('default', 'a/path.git', '') + non_valid = described_class.new('default', 'non-valid.git', '') - expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository) - end + expect { non_valid.checksum }.to raise_error(Gitlab::Git::Repository::InvalidRepository) end - context 'when calculate_checksum Gitaly feature is enabled' do - it_behaves_like 'calculating checksum' - end - - context 'when calculate_checksum Gitaly feature is disabled', :disable_gitaly do - it_behaves_like 'calculating checksum' - - describe 'when storage is broken', :broken_storage do - it 'raises a storage exception when storage is not available' do - broken_repo = described_class.new('broken', 'a/path.git', '') - - expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Storage::Inaccessible) - end - end - - it "raises a Gitlab::Git::Repository::Failure error if the `popen` call to git returns a non-zero exit code" do - allow(repository).to receive(:popen).and_return(['output', nil]) + it 'raises Gitlab::Git::Repository::NoRepository error when there is no repo' do + broken_repo = described_class.new('default', 'a/path.git', '') - expect { repository.checksum }.to raise_error Gitlab::Git::Repository::ChecksumError - end + expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository) end end -- cgit v1.2.3 From 73903ae8849c4ec833964ec8ad6b18d04083bd64 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 18 May 2018 14:07:06 +0100 Subject: Fix a RuntimeError: cannot modify frozen string --- lib/gitlab/git/blob.rb | 20 ++++++++++---------- lib/gitlab/git/path_helper.rb | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index d78d29b7ac6..156d077a69c 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -104,25 +104,22 @@ module Gitlab # file.rb # oid: 4a # # - # Blob.find_entry_by_path(repo, '1a', 'app/file.rb') # => '4a' + # Blob.find_entry_by_path(repo, '1a', 'blog', 'app', 'file.rb') # => '4a' # - def find_entry_by_path(repository, root_id, path) + def find_entry_by_path(repository, root_id, *path_parts) root_tree = repository.lookup(root_id) - # Strip leading slashes - path[%r{^/*}] = '' - path_arr = path.split('/') entry = root_tree.find do |entry| - entry[:name] == path_arr[0] + entry[:name] == path_parts[0] end return nil unless entry - if path_arr.size > 1 + if path_parts.size > 1 return nil unless entry[:type] == :tree - path_arr.shift - find_entry_by_path(repository, entry[:oid], path_arr.join('/')) + path_parts.shift + find_entry_by_path(repository, entry[:oid], *path_parts) else [:blob, :commit].include?(entry[:type]) ? entry : nil end @@ -185,10 +182,13 @@ module Gitlab def find_by_rugged(repository, sha, path, limit:) return unless path + # Strip any leading / characters from the path + path = path.sub(%r{\A/*}, '') + rugged_commit = repository.lookup(sha) root_tree = rugged_commit.tree - blob_entry = find_entry_by_path(repository, root_tree.oid, path) + blob_entry = find_entry_by_path(repository, root_tree.oid, *path.split('/')) return nil unless blob_entry diff --git a/lib/gitlab/git/path_helper.rb b/lib/gitlab/git/path_helper.rb index 155cf52f050..57b82a37d6c 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[%r{^/*}] = '' + filename = filename.sub(%r{\A/*}, '') # Expand relative paths (e.g. foo/../bar) filename = Pathname.new(filename) -- cgit v1.2.3 From 0a581fcfa28696245be297da39b12207a8ae0255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 May 2018 13:18:06 +0200 Subject: Minimize CE/EE difference in Gitlab::Auth::Saml::Config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/auth/saml/config.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb index 2760b1a3247..5fa9581f837 100644 --- a/lib/gitlab/auth/saml/config.rb +++ b/lib/gitlab/auth/saml/config.rb @@ -14,6 +14,10 @@ module Gitlab def external_groups options[:external_groups] end + + def admin_groups + options[:admin_groups] + end end end end -- cgit v1.2.3 From 37cd2b9b4dcf4c0145fd1bc2e19c8e903b4fd4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 May 2018 13:19:20 +0200 Subject: Minimize CE/EE difference in Gitlab::Auth::Saml::User MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/auth/saml/user.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/auth/saml/user.rb b/lib/gitlab/auth/saml/user.rb index cb01cd8004c..b8c84c37cd5 100644 --- a/lib/gitlab/auth/saml/user.rb +++ b/lib/gitlab/auth/saml/user.rb @@ -20,10 +20,8 @@ module Gitlab user ||= find_or_build_ldap_user if auto_link_ldap_user? user ||= build_new_user if signup_enabled? - if external_users_enabled? && user - # Check if there is overlap between the user's groups and the external groups - # setting then set user as external or internal. - user.external = !(auth_hash.groups & saml_config.external_groups).empty? + if user + user.external = !(auth_hash.groups & saml_config.external_groups).empty? if external_users_enabled? end user -- cgit v1.2.3 From dfdbf198b34075d0d7c88130ba3a082083e905c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 May 2018 14:00:44 +0200 Subject: Minimize CE/EE difference in Gitlab::Auth::UserAuthFinders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/auth/user_auth_finders.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb index cf02030c577..4dc23f977da 100644 --- a/lib/gitlab/auth/user_auth_finders.rb +++ b/lib/gitlab/auth/user_auth_finders.rb @@ -1,9 +1,5 @@ module Gitlab module Auth - # - # Exceptions - # - AuthenticationError = Class.new(StandardError) MissingTokenError = Class.new(AuthenticationError) TokenNotFoundError = Class.new(AuthenticationError) @@ -61,6 +57,12 @@ module Gitlab private + def route_authentication_setting + return {} unless respond_to?(:route_setting) + + route_setting(:authentication) || {} + end + def access_token strong_memoize(:access_token) do find_oauth_access_token || find_personal_access_token -- cgit v1.2.3 From 8b287679a1900185ecb6354ecdc8ac6d5a1e9ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 May 2018 16:26:44 +0200 Subject: Minimize CE/EE difference in Gitlab::Auth::LDAP::Access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/auth/ldap/access.rb | 41 ++++++++++++++++++++++++-------- spec/lib/gitlab/auth/ldap/access_spec.rb | 13 +++++++++- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/auth/ldap/access.rb b/lib/gitlab/auth/ldap/access.rb index 34286900e72..865185eb5db 100644 --- a/lib/gitlab/auth/ldap/access.rb +++ b/lib/gitlab/auth/ldap/access.rb @@ -6,7 +6,7 @@ module Gitlab module Auth module LDAP class Access - attr_reader :provider, :user + attr_reader :provider, :user, :ldap_identity def self.open(user, &block) Gitlab::Auth::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter| @@ -14,9 +14,12 @@ module Gitlab end end - def self.allowed?(user) + def self.allowed?(user, options = {}) self.open(user) do |access| + # Whether user is allowed, or not, we should update + # permissions to keep things clean if access.allowed? + access.update_user Users::UpdateService.new(user, user: user, last_credential_check_at: Time.now).execute true @@ -29,7 +32,8 @@ module Gitlab def initialize(user, adapter = nil) @adapter = adapter @user = user - @provider = user.ldap_identity.provider + @ldap_identity = user.ldap_identity + @provider = adapter&.provider || ldap_identity&.provider end def allowed? @@ -40,7 +44,7 @@ module Gitlab end # Block user in GitLab if he/she was blocked in AD - if Gitlab::Auth::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) + if Gitlab::Auth::LDAP::Person.disabled_via_active_directory?(ldap_identity.extern_uid, adapter) block_user(user, 'is disabled in Active Directory') false else @@ -64,27 +68,44 @@ module Gitlab Gitlab::Auth::LDAP::Config.new(provider) end + def find_ldap_user + Gitlab::Auth::LDAP::Person.find_by_dn(ldap_identity.extern_uid, adapter) + end + def ldap_user - @ldap_user ||= Gitlab::Auth::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) + return unless provider + + @ldap_user ||= find_ldap_user end def block_user(user, reason) user.ldap_block - Gitlab::AppLogger.info( - "LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \ - "blocking Gitlab user \"#{user.name}\" (#{user.email})" - ) + if provider + Gitlab::AppLogger.info( + "LDAP account \"#{ldap_identity.extern_uid}\" #{reason}, " \ + "blocking Gitlab user \"#{user.name}\" (#{user.email})" + ) + else + Gitlab::AppLogger.info( + "Account is not provided by LDAP, " \ + "blocking Gitlab user \"#{user.name}\" (#{user.email})" + ) + end end def unblock_user(user, reason) user.activate Gitlab::AppLogger.info( - "LDAP account \"#{user.ldap_identity.extern_uid}\" #{reason}, " \ + "LDAP account \"#{ldap_identity.extern_uid}\" #{reason}, " \ "unblocking Gitlab user \"#{user.name}\" (#{user.email})" ) end + + def update_user + # no-op in CE + end end end end diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb index 6b251d824f7..eff21985108 100644 --- a/spec/lib/gitlab/auth/ldap/access_spec.rb +++ b/spec/lib/gitlab/auth/ldap/access_spec.rb @@ -8,6 +8,7 @@ describe Gitlab::Auth::LDAP::Access do describe '.allowed?' do it 'updates the users `last_credential_check_at' do + allow(access).to receive(:update_user) expect(access).to receive(:allowed?) { true } expect(described_class).to receive(:open).and_yield(access) @@ -16,12 +17,21 @@ describe Gitlab::Auth::LDAP::Access do end end + describe '#find_ldap_user' do + it 'finds a user by dn first' do + expect(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user) + + access.find_ldap_user + end + end + describe '#allowed?' do subject { access.allowed? } context 'when the user cannot be found' do before do allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(nil) end it { is_expected.to be_falsey } @@ -54,7 +64,7 @@ describe Gitlab::Auth::LDAP::Access do end end - context 'and has no disabled flag in active diretory' do + context 'and has no disabled flag in active directory' do before do allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false) end @@ -100,6 +110,7 @@ describe Gitlab::Auth::LDAP::Access do context 'when user cannot be found' do before do allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(nil) + allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(nil) end it { is_expected.to be_falsey } -- cgit v1.2.3 From 6226d19c711e34ed9fa6f8a61468cac7f10ba7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 May 2018 16:27:12 +0200 Subject: Minimize CE/EE difference in Gitlab::Auth::LDAP::Config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/auth/ldap/config.rb | 18 ++++++++++++++-- spec/lib/gitlab/auth/ldap/config_spec.rb | 36 +++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb index 77185f52ced..d4415eaa6dc 100644 --- a/lib/gitlab/auth/ldap/config.rb +++ b/lib/gitlab/auth/ldap/config.rb @@ -11,6 +11,8 @@ module Gitlab attr_accessor :provider, :options + InvalidProvider = Class.new(StandardError) + def self.enabled? Gitlab.config.ldap.enabled end @@ -22,6 +24,10 @@ module Gitlab def self.available_servers return [] unless enabled? + _available_servers + end + + def self._available_servers Array.wrap(servers.first) end @@ -34,7 +40,7 @@ module Gitlab end def self.invalid_provider(provider) - raise "Unknown provider (#{provider}). Available providers: #{providers}" + raise InvalidProvider.new("Unknown provider (#{provider}). Available providers: #{providers}") end def initialize(provider) @@ -84,13 +90,17 @@ module Gitlab end def base - options['base'] + @base ||= Person.normalize_dn(options['base']) end def uid options['uid'] end + def label + options['label'] + end + def sync_ssh_keys? sync_ssh_keys.present? end @@ -132,6 +142,10 @@ module Gitlab options['timeout'].to_i end + def external_groups + options['external_groups'] || [] + end + def has_auth? options['password'] || options['bind_dn'] end diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb index 82587e2ba55..d3ab599d5a0 100644 --- a/spec/lib/gitlab/auth/ldap/config_spec.rb +++ b/spec/lib/gitlab/auth/ldap/config_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Auth::LDAP::Config do end it 'raises an error if a unknown provider is used' do - expect { described_class.new 'unknown' }.to raise_error(RuntimeError) + expect { described_class.new 'unknown' }.to raise_error(described_class::InvalidProvider) end end @@ -370,4 +370,38 @@ describe Gitlab::Auth::LDAP::Config do }) end end + + describe '#base' do + context 'when the configured base is not normalized' do + it 'returns the normalized base' do + stub_ldap_config(options: { 'base' => 'DC=example, DC= com' }) + + expect(config.base).to eq('dc=example,dc=com') + end + end + + context 'when the configured base is normalized' do + it 'returns the base unaltered' do + stub_ldap_config(options: { 'base' => 'dc=example,dc=com' }) + + expect(config.base).to eq('dc=example,dc=com') + end + end + + context 'when the configured base is malformed' do + it 'returns the base unaltered' do + stub_ldap_config(options: { 'base' => 'invalid,dc=example,dc=com' }) + + expect(config.base).to eq('invalid,dc=example,dc=com') + end + end + + context 'when the configured base is blank' do + it 'returns the base unaltered' do + stub_ldap_config(options: { 'base' => '' }) + + expect(config.base).to eq('') + end + end + end end -- cgit v1.2.3 From d34d6a58fd89f72b63842832f8a6660a01681ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 May 2018 16:27:52 +0200 Subject: Minimize CE/EE difference in Gitlab::Auth::LDAP::User MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/lib/gitlab/auth/ldap/user_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb index 653c19942ea..44bb9d20e47 100644 --- a/spec/lib/gitlab/auth/ldap/user_spec.rb +++ b/spec/lib/gitlab/auth/ldap/user_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Auth::LDAP::User do + include LdapHelpers + let(:ldap_user) { described_class.new(auth_hash) } let(:gl_user) { ldap_user.gl_user } let(:info) do @@ -177,8 +179,7 @@ describe Gitlab::Auth::LDAP::User do describe 'blocking' do def configure_block(value) - allow_any_instance_of(Gitlab::Auth::LDAP::Config) - .to receive(:block_auto_created_users).and_return(value) + stub_ldap_config(block_auto_created_users: value) end context 'signup' do -- cgit v1.2.3 From 55e2ce762d52e680b45c9b87a238f993485f2866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 18 May 2018 14:34:08 +0000 Subject: Revert "Improve cacheable module" This reverts commit 2ebafdfb2f026580153fd2cf50f4b6b7ab3a0344 --- app/models/concerns/redis_cacheable.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index 53f022e2b35..b5425295130 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -7,11 +7,11 @@ module RedisCacheable class_methods do def cached_attr_reader(*attributes) attributes.each do |attribute| - unless self.column_names.include?(attribute.to_s) - raise ArgumentError, "`cached_attr_reader` requires the #{self.name}##{attribute} to be a database attribute" - end - define_method(attribute) do + unless self.has_attribute?(attribute) + raise ArgumentError, "`cached_attr_reader` requires the #{self.class.name}\##{attribute} attribute to have a database column" + end + cached_attribute(attribute) || read_attribute(attribute) end end @@ -50,9 +50,7 @@ module RedisCacheable if Gitlab.rails5? self.class.type_for_attribute(attribute).cast(value) else - ActiveSupport::Deprecation.silence do - self.class.column_for_attribute(attribute).type_cast_from_database(value) - end + self.class.column_for_attribute(attribute).type_cast_from_database(value) end end end -- cgit v1.2.3 From f2479b608c042b2c4a7b85e38da92834c5c5ceb2 Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Fri, 18 May 2018 14:27:30 +0000 Subject: Add web shortcut to docs and shortcut modal --- app/views/help/_shortcuts.html.haml | 11 +++++++++++ changelogs/unreleased/jr-web-ide-shortcuts.yml | 5 +++++ doc/workflow/shortcuts.md | 6 ++++++ 3 files changed, 22 insertions(+) create mode 100644 changelogs/unreleased/jr-web-ide-shortcuts.yml diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 2244d16f0a6..9a3a03a7671 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -231,6 +231,17 @@ %td.shortcut .key y %td Go to file permalink + %tbody + %tr + %th + %th Web IDE + %tr + %td.shortcut + - if browser.platform.mac? + .key ⌘ p + - else + .key ctrl p + %td Go to file .col-lg-4 %table.shortcut-mappings %tbody.hidden-shortcut.network{ style: 'display:none' } diff --git a/changelogs/unreleased/jr-web-ide-shortcuts.yml b/changelogs/unreleased/jr-web-ide-shortcuts.yml new file mode 100644 index 00000000000..a895eab432a --- /dev/null +++ b/changelogs/unreleased/jr-web-ide-shortcuts.yml @@ -0,0 +1,5 @@ +--- +title: Add shortcuts to Web IDE docs and modal +merge_request: 19044 +author: +type: changed diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 930f2e73683..c99505e6bdf 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -88,3 +88,9 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | Keyboard Shortcut | Description | | ----------------- | ----------- | | e | Edit wiki page| + +## Web IDE + +| Keyboard Shortcut | Description | +| ----------------- | ----------- | +| + p | Go to file | -- cgit v1.2.3 From dbe0839396f56e30780350e840a1ded303dfbb81 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Fri, 18 May 2018 10:29:20 -0500 Subject: Fixes deploy tokens build variables It was using name, instead of username. Fixes documentation as well Closes #46454 --- app/models/ci/build.rb | 2 +- changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml | 5 +++++ doc/user/project/deploy_tokens/index.md | 2 +- spec/models/ci/build_spec.rb | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index d9649e30edc..1c42ed4d3e5 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -661,7 +661,7 @@ module Ci Gitlab::Ci::Variables::Collection.new.tap do |variables| break variables unless gitlab_deploy_token - variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.name) + variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username) variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false) end end diff --git a/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml b/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml new file mode 100644 index 00000000000..e610e53f71c --- /dev/null +++ b/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml @@ -0,0 +1,5 @@ +--- +title: Fixes deploy token variables on Ci::Build +merge_request: 19047 +author: +type: fixed diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 7a8b3c75690..c09d5aeba8e 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -76,7 +76,7 @@ pull images from your Container Registry. > [Introduced][ce-18414] in GitLab 10.8. There's a special case when it comes to Deploy Tokens, if a user creates one -named `gitlab-deploy-token`, the name and token of the Deploy Token will be +named `gitlab-deploy-token`, the username and token of the Deploy Token will be automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD`, respectively. diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7d8bddbcedb..af5f5047803 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2084,7 +2084,7 @@ describe Ci::Build do let(:deploy_token_variables) do [ - { key: 'CI_DEPLOY_USER', value: deploy_token.name, public: true }, + { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true }, { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false } ] end -- cgit v1.2.3 From 1fb431c6acfa0bdc578a6488147ed5f28aaa96c3 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Fri, 18 May 2018 09:41:25 -0700 Subject: Use defaults for retry And use a supported syntax for ignoring docs during gem install --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cc6fd5e2bfe..c0c1f3c8ec3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -307,9 +307,8 @@ cloud-native-image: stage: build allow_failure: true cache: {} - retry: 0 before_script: - - gem install gitlab --no-doc + - gem install gitlab --no-rdoc --no-ri - chmod 755 ./scripts/trigger-build-cloud-native script: - ./scripts/trigger-build-cloud-native -- cgit v1.2.3 From 12292502317ea1ef106c2236d6ab7012bf9d7825 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 18 May 2018 19:29:17 +0200 Subject: Chmod 644 on offending files --- app/views/projects/forks/new.html.haml | 0 doc/user/admin_area/settings/img/enforce_terms.png | Bin doc/user/admin_area/settings/img/respond_to_terms.png | Bin 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 app/views/projects/forks/new.html.haml mode change 100755 => 100644 doc/user/admin_area/settings/img/enforce_terms.png mode change 100755 => 100644 doc/user/admin_area/settings/img/respond_to_terms.png diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml old mode 100755 new mode 100644 diff --git a/doc/user/admin_area/settings/img/enforce_terms.png b/doc/user/admin_area/settings/img/enforce_terms.png old mode 100755 new mode 100644 diff --git a/doc/user/admin_area/settings/img/respond_to_terms.png b/doc/user/admin_area/settings/img/respond_to_terms.png old mode 100755 new mode 100644 -- cgit v1.2.3 From 019f5e2469f21c4127a2c972042839185b26bb3f Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Tue, 20 Mar 2018 20:20:12 +0100 Subject: Add handling for commit/tags with big messages --- .flayignore | 4 + lib/gitlab/git/commit.rb | 64 ++++++++++++++-- lib/gitlab/git/repository.rb | 7 +- lib/gitlab/git/repository_mirroring.rb | 6 +- lib/gitlab/git/tag.rb | 88 +++++++++++++++++++++- lib/gitlab/gitaly_client/commit_service.rb | 16 ++++ lib/gitlab/gitaly_client/operation_service.rb | 2 +- lib/gitlab/gitaly_client/ref_service.rb | 18 ++++- lib/gitlab/gitaly_client/util.rb | 14 ---- spec/factories/gitaly/tag.rb | 9 +++ spec/lib/gitlab/git/commit_spec.rb | 56 +++++++++++++- spec/lib/gitlab/git/tag_spec.rb | 52 +++++++++++++ .../projects/update_remote_mirror_service_spec.rb | 6 +- 13 files changed, 310 insertions(+), 32 deletions(-) create mode 100644 spec/factories/gitaly/tag.rb diff --git a/.flayignore b/.flayignore index 0c4eee10ffa..7faa6c7bb90 100644 --- a/.flayignore +++ b/.flayignore @@ -10,3 +10,7 @@ lib/gitlab/background_migration/* app/models/project_services/kubernetes_service.rb lib/gitlab/workhorse.rb lib/gitlab/ci/trace/chunked_io.rb +lib/gitlab/gitaly_client/ref_service.rb +lib/gitlab/gitaly_client/commit_service.rb +lib/gitlab/git/commit.rb +lib/gitlab/git/tag.rb diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index d79a4dbeee4..89f761dd515 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -6,6 +6,7 @@ module Gitlab attr_accessor :raw_commit, :head + MAX_COMMIT_MESSAGE_DISPLAY_SIZE = 10.megabytes MIN_SHA_LENGTH = 7 SERIALIZE_KEYS = [ :id, :message, :parent_ids, @@ -63,9 +64,7 @@ module Gitlab if is_enabled repo.gitaly_commit_client.find_commit(commit_id) else - obj = repo.rev_parse_target(commit_id) - - obj.is_a?(Rugged::Commit) ? obj : nil + rugged_find(repo, commit_id) end end @@ -76,6 +75,12 @@ module Gitlab nil end + def rugged_find(repo, commit_id) + obj = repo.rev_parse_target(commit_id) + + obj.is_a?(Rugged::Commit) ? obj : nil + end + # Get last commit for HEAD # # Ex. @@ -297,11 +302,40 @@ module Gitlab nil end end + + def get_message(repository, commit_id) + BatchLoader.for({ repository: repository, commit_id: commit_id }).batch do |items, loader| + items_by_repo = items.group_by { |i| i[:repository] } + + items_by_repo.each do |repo, items| + commit_ids = items.map { |i| i[:commit_id] } + + messages = get_messages(repository, commit_ids) + + messages.each do |commit_sha, message| + loader.call({ repository: repository, commit_id: commit_sha }, message) + end + end + end + end + + def get_messages(repository, commit_ids) + repository.gitaly_migrate(:commit_messages) do |is_enabled| + if is_enabled + repository.gitaly_commit_client.get_commit_messages(commit_ids) + else + commit_ids.map { |id| [id, rugged_find(repository, id).message] }.to_h + end + end + end end def initialize(repository, raw_commit, head = nil) raise "Nil as raw commit passed" unless raw_commit + @repository = repository + @head = head + case raw_commit when Hash init_from_hash(raw_commit) @@ -312,9 +346,6 @@ module Gitlab else raise "Invalid raw commit type: #{raw_commit.class}" end - - @repository = repository - @head = head end def sha @@ -518,7 +549,7 @@ module Gitlab # TODO: Once gitaly "takes over" Rugged consider separating the # subject from the message to make it clearer when there's one # available but not the other. - @message = (commit.body.presence || commit.subject).dup + @message = message_from_gitaly_body @authored_date = Time.at(commit.author.date.seconds).utc @author_name = commit.author.name.dup @author_email = commit.author.email.dup @@ -570,6 +601,25 @@ module Gitlab def refs(repo) repo.refs_hash[id] end + + def message_from_gitaly_body + return @raw_commit.subject.dup if @raw_commit.body_size.zero? + return @raw_commit.body.dup if full_body_fetched_from_gitaly? + + if @raw_commit.body_size > MAX_COMMIT_MESSAGE_DISPLAY_SIZE + "#{@raw_commit.subject}\n\n--commit message is too big".strip + else + fetch_body_from_gitaly + end + end + + def full_body_fetched_from_gitaly? + @raw_commit.body.bytesize == @raw_commit.body_size + end + + def fetch_body_from_gitaly + self.class.get_message(@repository, id) + end end end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 061865a7acf..b9aa4d03d9f 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1948,7 +1948,12 @@ module Gitlab end target_commit = Gitlab::Git::Commit.find(self, ref.target) - Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message) + Gitlab::Git::Tag.new(self, { + name: ref.name, + target: ref.target, + target_commit: target_commit, + message: message + }) end.sort_by(&:name) end diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb index 8a01f92e2af..e35ea5762eb 100644 --- a/lib/gitlab/git/repository_mirroring.rb +++ b/lib/gitlab/git/repository_mirroring.rb @@ -35,7 +35,11 @@ module Gitlab next if name =~ /\^\{\}\Z/ target_commit = Gitlab::Git::Commit.find(self, target) - Gitlab::Git::Tag.new(self, name, target, target_commit) + Gitlab::Git::Tag.new(self, { + name: name, + target: target, + target_commit: target_commit + }) end.compact end diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb index 8a8f7b051ed..e44284572fd 100644 --- a/lib/gitlab/git/tag.rb +++ b/lib/gitlab/git/tag.rb @@ -1,17 +1,99 @@ module Gitlab module Git class Tag < Ref - attr_reader :object_sha + extend Gitlab::EncodingHelper + + attr_reader :object_sha, :repository + + MAX_TAG_MESSAGE_DISPLAY_SIZE = 10.megabytes + SERIALIZE_KEYS = %i[name target target_commit message].freeze + + attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator + + class << self + def get_message(repository, tag_id) + BatchLoader.for({ repository: repository, tag_id: tag_id }).batch do |items, loader| + items_by_repo = items.group_by { |i| i[:repository] } + + items_by_repo.each do |repo, items| + tag_ids = items.map { |i| i[:tag_id] } + + messages = get_messages(repository, tag_ids) + + messages.each do |id, message| + loader.call({ repository: repository, tag_id: id }, message) + end + end + end + end + + def get_messages(repository, tag_ids) + repository.gitaly_migrate(:tag_messages) do |is_enabled| + if is_enabled + repository.gitaly_ref_client.get_tag_messages(tag_ids) + else + tag_ids.map do |id| + tag = repository.rugged.lookup(id) + message = tag.is_a?(Rugged::Commit) ? "" : tag.message + + [id, message] + end.to_h + end + end + end + end + + def initialize(repository, raw_tag) + @repository = repository + @raw_tag = raw_tag + + case raw_tag + when Hash + init_from_hash + when Gitaly::Tag + init_from_gitaly + end - def initialize(repository, name, target, target_commit, message = nil) super(repository, name, target, target_commit) + end + + def init_from_hash + raw_tag = @raw_tag.symbolize_keys + + SERIALIZE_KEYS.each do |key| + send("#{key}=", raw_tag[key]) # rubocop:disable GitlabSecurity/PublicSend + end + end + + def init_from_gitaly + @name = encode!(@raw_tag.name.dup) + @target = @raw_tag.id + @message = message_from_gitaly_tag - @message = message + if @raw_tag.target_commit.present? + @target_commit = Gitlab::Git::Commit.decorate(repository, @raw_tag.target_commit) + end end def message encode! @message end + + private + + def message_from_gitaly_tag + return @raw_tag.message.dup if full_message_fetched_from_gitaly? + + if @raw_tag.message_size > MAX_TAG_MESSAGE_DISPLAY_SIZE + '--tag message is too big' + else + self.class.get_message(@repository, target) + end + end + + def full_message_fetched_from_gitaly? + @raw_tag.message.bytesize == @raw_tag.message_size + end end end end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index a36e6c822f9..1f5f88bf792 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -334,6 +334,22 @@ module Gitlab signatures end + def get_commit_messages(commit_ids) + request = Gitaly::GetCommitMessagesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids) + response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_messages, request) + + messages = Hash.new { |h, k| h[k] = ''.b } + current_commit_id = nil + + response.each do |rpc_message| + current_commit_id = rpc_message.commit_id if rpc_message.commit_id.present? + + messages[current_commit_id] << rpc_message.message + end + + messages + end + private def call_commit_diff(request_params, options = {}) diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 831cfd1e014..44b0e517bf0 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -40,7 +40,7 @@ module Gitlab raise Gitlab::Git::Repository::TagExistsError end - Util.gitlab_tag_from_gitaly_tag(@repository, response.tag) + Gitlab::Git::Tag.new(@repository, response.tag) rescue GRPC::FailedPrecondition => e raise Gitlab::Git::Repository::InvalidRef, e end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index ba6b577fd17..3ac46be6208 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -171,6 +171,22 @@ module Gitlab consume_ref_contains_sha_response(stream, :branch_names) end + def get_tag_messages(tag_ids) + request = Gitaly::GetTagMessagesRequest.new(repository: @gitaly_repo, tag_ids: tag_ids) + response = GitalyClient.call(@repository.storage, :ref_service, :get_tag_messages, request) + + messages = Hash.new { |h, k| h[k] = ''.b } + current_tag_id = nil + + response.each do |rpc_message| + current_tag_id = rpc_message.tag_id if rpc_message.tag_id.present? + + messages[current_tag_id] << rpc_message.message + end + + messages + end + private def consume_refs_response(response) @@ -210,7 +226,7 @@ module Gitlab def consume_tags_response(response) response.flat_map do |message| - message.tags.map { |gitaly_tag| Util.gitlab_tag_from_gitaly_tag(@repository, gitaly_tag) } + message.tags.map { |gitaly_tag| Gitlab::Git::Tag.new(@repository, gitaly_tag) } end end diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb index 405567db94a..9c19c51d412 100644 --- a/lib/gitlab/gitaly_client/util.rb +++ b/lib/gitlab/gitaly_client/util.rb @@ -21,20 +21,6 @@ module Gitlab gitaly_repository.relative_path, gitaly_repository.gl_repository) end - - def gitlab_tag_from_gitaly_tag(repository, gitaly_tag) - if gitaly_tag.target_commit.present? - commit = Gitlab::Git::Commit.decorate(repository, gitaly_tag.target_commit) - end - - Gitlab::Git::Tag.new( - repository, - Gitlab::EncodingHelper.encode!(gitaly_tag.name.dup), - gitaly_tag.id, - commit, - Gitlab::EncodingHelper.encode!(gitaly_tag.message.chomp) - ) - end end end end diff --git a/spec/factories/gitaly/tag.rb b/spec/factories/gitaly/tag.rb new file mode 100644 index 00000000000..e99776d524a --- /dev/null +++ b/spec/factories/gitaly/tag.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :gitaly_tag, class: Gitaly::Tag do + skip_create + + name { 'v3.1.4' } + message { 'Pie release' } + target_commit factory: :gitaly_commit + end +end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 2e068584c2e..08c6d1e55e9 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -66,7 +66,8 @@ describe Gitlab::Git::Commit, seed_helper: true do describe "Commit info from gitaly commit" do let(:subject) { "My commit".force_encoding('ASCII-8BIT') } let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } - let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body) } + let(:body_size) { body.length } + let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body, body_size: body_size) } let(:id) { gitaly_commit.id } let(:committer) { gitaly_commit.committer } let(:author) { gitaly_commit.author } @@ -83,10 +84,30 @@ describe Gitlab::Git::Commit, seed_helper: true do it { expect(commit.committer_email).to eq(committer.email) } it { expect(commit.parent_ids).to eq(gitaly_commit.parent_ids) } - context 'no body' do + context 'body_size != body.size' do let(:body) { "".force_encoding('ASCII-8BIT') } - it { expect(commit.safe_message).to eq(subject) } + context 'zero body_size' do + it { expect(commit.safe_message).to eq(subject) } + end + + context 'body_size less than threshold' do + let(:body_size) { 123 } + + it 'fetches commit message seperately' do + expect(described_class).to receive(:get_message).with(repository, id) + + commit.safe_message + end + end + + context 'body_size greater than threshold' do + let(:body_size) { described_class::MAX_COMMIT_MESSAGE_DISPLAY_SIZE + 1 } + + it 'returns the suject plus a notice about message size' do + expect(commit.safe_message).to eq("My commit\n\n--commit message is too big") + end + end end end @@ -589,6 +610,35 @@ describe Gitlab::Git::Commit, seed_helper: true do it { is_expected.not_to include("feature") } end + describe '.get_message' do + let(:commit_ids) { %w[6d394385cf567f80a8fd85055db1ab4c5295806f cfe32cf61b73a0d5e9f13e774abde7ff789b1660] } + + subject do + commit_ids.map { |id| described_class.get_message(repository, id) } + end + + shared_examples 'getting commit messages' do + it 'gets commit messages' do + expect(subject).to contain_exactly( + "Added contributing guide\n\nSigned-off-by: Dmitriy Zaporozhets \n", + "Add submodule\n\nSigned-off-by: Dmitriy Zaporozhets \n" + ) + end + end + + context 'when Gitaly commit_messages feature is enabled' do + it_behaves_like 'getting commit messages' + + it 'gets messages in one batch', :request_store do + expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1) + end + end + + context 'when Gitaly commit_messages feature is disabled', :disable_gitaly do + it_behaves_like 'getting commit messages' + end + end + def sample_commit_hash { author_email: "dmitriy.zaporozhets@gmail.com", diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index 6c4f538bf01..be2f5bfb819 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -32,4 +32,56 @@ describe Gitlab::Git::Tag, seed_helper: true do context 'when Gitaly tags feature is disabled', :skip_gitaly_mock do it_behaves_like 'Gitlab::Git::Repository#tags' end + + describe '.get_message' do + let(:tag_ids) { %w[f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b] } + + subject do + tag_ids.map { |id| described_class.get_message(repository, id) } + end + + shared_examples 'getting tag messages' do + it 'gets tag messages' do + expect(subject[0]).to eq("Release\n") + expect(subject[1]).to eq("Version 1.1.0\n") + end + end + + context 'when Gitaly tag_messages feature is enabled' do + it_behaves_like 'getting tag messages' + + it 'gets messages in one batch', :request_store do + expect { subject.map(&:itself) }.to change { Gitlab::GitalyClient.get_request_count }.by(1) + end + end + + context 'when Gitaly tag_messages feature is disabled', :disable_gitaly do + it_behaves_like 'getting tag messages' + end + end + + describe 'tag into from Gitaly tag' do + context 'message_size != message.size' do + let(:gitaly_tag) { build(:gitaly_tag, message: ''.b, message_size: message_size) } + let(:tag) { described_class.new(repository, gitaly_tag) } + + context 'message_size less than threshold' do + let(:message_size) { 123 } + + it 'fetches tag message seperately' do + expect(described_class).to receive(:get_message).with(repository, gitaly_tag.id) + + tag.message + end + end + + context 'message_size greater than threshold' do + let(:message_size) { described_class::MAX_TAG_MESSAGE_DISPLAY_SIZE + 1 } + + it 'returns a notice about message size' do + expect(tag.message).to eq("--tag message is too big") + end + end + end + end end diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb index be09afd9f36..723cb374c37 100644 --- a/spec/services/projects/update_remote_mirror_service_spec.rb +++ b/spec/services/projects/update_remote_mirror_service_spec.rb @@ -343,7 +343,11 @@ describe Projects::UpdateRemoteMirrorService do tag = repository.find_tag(name) target = tag.try(:target) target_commit = tag.try(:dereferenced_target) - tags << Gitlab::Git::Tag.new(repository.raw_repository, name, target, target_commit) + tags << Gitlab::Git::Tag.new(repository.raw_repository, { + name: name, + target: target, + target_commit: target_commit + }) end end -- cgit v1.2.3 From 1b530f96645b738c8e723f6ff8841569ac06aef4 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 18 May 2018 20:14:31 +0000 Subject: Fix double brackets being linkified in wiki markdown --- .../18524-fix-double-brackets-in-wiki-markdown.yml | 5 +++++ lib/banzai/filter/gollum_tags_filter.rb | 3 +++ .../projects/wiki/markdown_preview_spec.rb | 23 ++++++++++++++++++++++ spec/lib/banzai/filter/gollum_tags_filter_spec.rb | 6 ++++++ 4 files changed, 37 insertions(+) create mode 100644 changelogs/unreleased/18524-fix-double-brackets-in-wiki-markdown.yml diff --git a/changelogs/unreleased/18524-fix-double-brackets-in-wiki-markdown.yml b/changelogs/unreleased/18524-fix-double-brackets-in-wiki-markdown.yml new file mode 100644 index 00000000000..9287243a7e3 --- /dev/null +++ b/changelogs/unreleased/18524-fix-double-brackets-in-wiki-markdown.yml @@ -0,0 +1,5 @@ +--- +title: Fix double-brackets being linkified in wiki markdown +merge_request: 18524 +author: brewingcode +type: fixed diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index f2e9a5a1116..4bc82ecb4d6 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -58,6 +58,9 @@ module Banzai def call doc.search(".//text()").each do |node| + # Do not perform linking inside blocks + next unless node.ancestors('code').empty? + # A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running # before this one, it will be converted into `[[TOC]]`, so it # needs special-case handling diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 6586ccaa400..e473739a6aa 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -155,4 +155,27 @@ feature 'Projects > Wiki > User previews markdown changes', :js do end end end + + it "does not linkify double brackets inside code blocks as expected" do + click_link 'New page' + page.within '#modal-new-wiki' do + fill_in :new_wiki_path, with: 'linkify_test' + click_button 'Create page' + end + + page.within '.wiki-form' do + fill_in :wiki_content, with: <<-HEREDOC + `[[do_not_linkify]]` + ``` + [[also_do_not_linkify]] + ``` + HEREDOC + click_on "Preview" + end + + expect(page).to have_content("do_not_linkify") + + expect(page.html).to include('[[do_not_linkify]]') + expect(page.html).to include('[[also_do_not_linkify]]') + end end diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index ca76d6f0881..0e178b859c4 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -91,6 +91,12 @@ describe Banzai::Filter::GollumTagsFilter do expect(doc.at_css('a').text).to eq 'link-text' expect(doc.at_css('a')['href']).to eq expected_path end + + it "inside back ticks will be exempt from linkification" do + doc = filter('[[link-in-backticks]]', project_wiki: project_wiki) + + expect(doc.at_css('code').text).to eq '[[link-in-backticks]]' + end end context 'table of contents' do -- cgit v1.2.3 From d8a9bfb6b7e17812915fe6767158abaca2ea6a74 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Thu, 5 Apr 2018 23:56:32 +0900 Subject: Order UsersController#projects.json by updated_at --- app/finders/personal_projects_finder.rb | 2 +- changelogs/unreleased/45065-users-projects-json-sort.yml | 5 +++++ .../users/user_browses_projects_on_user_page_spec.rb | 8 ++++---- spec/finders/personal_projects_finder_spec.rb | 12 +++++++----- 4 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/45065-users-projects-json-sort.yml diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb index 3ad4bd5f066..5aea0cb8192 100644 --- a/app/finders/personal_projects_finder.rb +++ b/app/finders/personal_projects_finder.rb @@ -13,7 +13,7 @@ class PersonalProjectsFinder < UnionFinder def execute(current_user = nil) segments = all_projects(current_user) - find_union(segments, Project).includes(:namespace).order_id_desc + find_union(segments, Project).includes(:namespace).order_updated_desc end private diff --git a/changelogs/unreleased/45065-users-projects-json-sort.yml b/changelogs/unreleased/45065-users-projects-json-sort.yml new file mode 100644 index 00000000000..89a1d7eb36f --- /dev/null +++ b/changelogs/unreleased/45065-users-projects-json-sort.yml @@ -0,0 +1,5 @@ +--- +title: Order UsersController#projects.json by updated_at +merge_request: 18227 +author: Takuya Noguchi +type: other diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb index a70637c8370..7bede0b0d48 100644 --- a/spec/features/users/user_browses_projects_on_user_page_spec.rb +++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb @@ -27,8 +27,8 @@ describe 'Users > User browses projects on user page', :js do end it 'paginates projects', :js do - project = create(:project, namespace: user.namespace) - project2 = create(:project, namespace: user.namespace) + project = create(:project, namespace: user.namespace, updated_at: 2.minutes.since) + project2 = create(:project, namespace: user.namespace, updated_at: 1.minute.since) allow(Project).to receive(:default_per_page).and_return(1) sign_in(user) @@ -41,11 +41,11 @@ describe 'Users > User browses projects on user page', :js do wait_for_requests - expect(page).to have_content(project2.name) + expect(page).to have_content(project.name) click_link('Next') - expect(page).to have_content(project.name) + expect(page).to have_content(project2.name) end context 'when not signed in' do diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index 5e52898e9c0..00c551a1f65 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -4,14 +4,16 @@ describe PersonalProjectsFinder do let(:source_user) { create(:user) } let(:current_user) { create(:user) } let(:finder) { described_class.new(source_user) } - let!(:public_project) { create(:project, :public, namespace: source_user.namespace) } + let!(:public_project) do + create(:project, :public, namespace: source_user.namespace, updated_at: 1.hour.ago) + end let!(:private_project) do - create(:project, :private, namespace: source_user.namespace, path: 'mepmep') + create(:project, :private, namespace: source_user.namespace, updated_at: 3.hours.ago, path: 'mepmep') end let!(:internal_project) do - create(:project, :internal, namespace: source_user.namespace, path: 'C') + create(:project, :internal, namespace: source_user.namespace, updated_at: 2.hours.ago, path: 'C') end before do @@ -28,7 +30,7 @@ describe PersonalProjectsFinder do subject { finder.execute(current_user) } context 'normal user' do - it { is_expected.to eq([internal_project, private_project, public_project]) } + it { is_expected.to eq([public_project, internal_project, private_project]) } end context 'external' do @@ -36,7 +38,7 @@ describe PersonalProjectsFinder do current_user.update_attributes(external: true) end - it { is_expected.to eq([private_project, public_project]) } + it { is_expected.to eq([public_project, private_project]) } end end end -- cgit v1.2.3 From 100c687cbc49c592afc1c08bfda9e21d97a115b1 Mon Sep 17 00:00:00 2001 From: Sergey Sinev Date: Wed, 25 Apr 2018 20:37:52 +0300 Subject: Fix error when deleting an empty list of refs Closes #45743 --- .../xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml | 5 +++++ lib/gitlab/git/repository.rb | 2 +- spec/lib/gitlab/git/repository_spec.rb | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml diff --git a/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml b/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml new file mode 100644 index 00000000000..94da4d74300 --- /dev/null +++ b/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml @@ -0,0 +1,5 @@ +--- +title: Fix error when deleting an empty list of refs +merge_request: +author: +type: fixed diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index b521d69930a..5617d7c1c41 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -2346,7 +2346,7 @@ module Gitlab end def gitaly_delete_refs(*ref_names) - gitaly_ref_client.delete_refs(refs: ref_names) + gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any? end def rugged_remove_remote(remote_name) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 2b5710ac401..01a51573922 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -600,6 +600,10 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + it 'does not fail when deleting an empty list of refs' do + expect { repo.delete_refs(*[]) }.not_to raise_error + end + it 'raises an error if it failed' do expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError) end -- cgit v1.2.3 From d839b880a256f208ae5a0d877765bc5fcbfcd44d Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 14 May 2018 10:07:53 +0800 Subject: Add created_by_me and assigned_to_me scopes Deprecate corresponding dash versions created-by-me and assigned-to-me --- app/finders/issuable_finder.rb | 9 +- app/finders/issues_finder.rb | 2 +- app/finders/merge_requests_finder.rb | 2 +- .../unreleased/44799-api-naming-issue-scope.yml | 5 + doc/api/issues.md | 9 +- doc/api/merge_requests.md | 9 +- lib/api/issues.rb | 9 +- lib/api/merge_requests.rb | 9 +- spec/finders/issues_finder_spec.rb | 2 +- spec/requests/api/issues_spec.rb | 9 + spec/requests/api/merge_requests_spec.rb | 225 ++++++++------------- 11 files changed, 129 insertions(+), 161 deletions(-) create mode 100644 changelogs/unreleased/44799-api-naming-issue-scope.yml diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 7ed9b1fc6d0..c6ef79ce15e 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -6,7 +6,7 @@ # klass - actual class like Issue or MergeRequest # current_user - which user use # params: -# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# scope: 'created_by_me' or 'assigned_to_me' or 'all' # state: 'opened' or 'closed' or 'all' # group_id: integer # project_id: integer @@ -282,9 +282,9 @@ class IssuableFinder return items.none if current_user_related? && !current_user case params[:scope] - when 'created-by-me', 'authored' + when 'created_by_me', 'authored' items.where(author_id: current_user.id) - when 'assigned-to-me' + when 'assigned_to_me' items.assigned_to(current_user) else items @@ -426,6 +426,7 @@ class IssuableFinder end def current_user_related? - params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' + scope = params[:scope] + scope == 'created_by_me' || scope == 'authored' || scope == 'assigned_to_me' end end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 2a27ff0e386..1787b4899cd 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -5,7 +5,7 @@ # Arguments: # current_user - which user use # params: -# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# scope: 'created_by_me' or 'assigned_to_me' or 'all' # state: 'open' or 'closed' or 'all' # group_id: integer # project_id: integer diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 64dc1e6af0f..e2240e5e0d8 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -5,7 +5,7 @@ # Arguments: # current_user - which user use # params: -# scope: 'created-by-me' or 'assigned-to-me' or 'all' +# scope: 'created_by_me' or 'assigned_to_me' or 'all' # state: 'open', 'closed', 'merged', or 'all' # group_id: integer # project_id: integer diff --git a/changelogs/unreleased/44799-api-naming-issue-scope.yml b/changelogs/unreleased/44799-api-naming-issue-scope.yml new file mode 100644 index 00000000000..75c6ea4cd0d --- /dev/null +++ b/changelogs/unreleased/44799-api-naming-issue-scope.yml @@ -0,0 +1,5 @@ +--- +title: Rename issue scope created-by-me to created_by_me, and assigned-to-me to assigned_to_me +merge_request: 44799 +author: +type: deprecated diff --git a/doc/api/issues.md b/doc/api/issues.md index 7479c1d2f93..d0063e0b8a2 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -38,8 +38,8 @@ GET /issues?my_reaction_emoji=star | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `milestone` | string | no | The milestone title | -| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` _([Introduced][ce-13004] in GitLab 9.5)_ | -| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. _([Introduced][ce-13004] in GitLab 9.5)_ | +| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | +| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | @@ -152,7 +152,7 @@ GET /groups/:id/issues?my_reaction_emoji=star | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `milestone` | string | no | The milestone title | -| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ | +| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | @@ -266,7 +266,7 @@ GET /projects/:id/issues?my_reaction_emoji=star | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `milestone` | string | no | The milestone title | -| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ | +| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | @@ -1254,3 +1254,4 @@ Example response: [ce-13004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13004 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016 [ce-17042]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17042 +[ce-18935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18935 diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index b9a4f661777..cbd51c9870c 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -28,7 +28,7 @@ GET /merge_requests?milestone=release GET /merge_requests?labels=bug,reproduced GET /merge_requests?author_id=5 GET /merge_requests?my_reaction_emoji=star -GET /merge_requests?scope=assigned-to-me +GET /merge_requests?scope=assigned_to_me ``` Parameters: @@ -45,8 +45,8 @@ Parameters: | `created_before` | datetime | no | Return merge requests created on or before the given time | | `updated_after` | datetime | no | Return merge requests updated on or after the given time | | `updated_before` | datetime | no | Return merge requests updated on or before the given time | -| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` | -| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` | +| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. | +| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me` | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `source_branch` | string | no | Return merge requests with the given source branch | @@ -164,7 +164,7 @@ Parameters: | `created_before` | datetime | no | Return merge requests created on or before the given time | | `updated_after` | datetime | no | Return merge requests updated on or after the given time | | `updated_before` | datetime | no | Return merge requests updated on or before the given time | -| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ | +| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13060] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | @@ -1460,3 +1460,4 @@ Example response: [ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016 [ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454 +[ce-18935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18935 diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 12ff2a1398b..257369ee7b0 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -13,6 +13,7 @@ module API args.delete(:id) args[:milestone_title] = args.delete(:milestone) args[:label_name] = args.delete(:labels) + args[:scope] = args[:scope].underscore if args[:scope] issues = IssuesFinder.new(current_user, args).execute .preload(:assignees, :labels, :notes, :timelogs) @@ -36,8 +37,8 @@ module API optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time' optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID' optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID' - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], - desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], + desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' use :pagination end @@ -66,8 +67,8 @@ module API optional :state, type: String, values: %w[opened closed all], default: 'all', desc: 'Return opened, closed, or all issues' use :issues_params - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me', - desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me', + desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' end get do issues = paginate(find_issues) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d4cc18f622b..bc4df16e3a8 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -38,6 +38,7 @@ module API args[:milestone_title] = args.delete(:milestone) args[:label_name] = args.delete(:labels) + args[:scope] = args[:scope].underscore if args[:scope] merge_requests = MergeRequestsFinder.new(current_user, args).execute .reorder(args[:order_by] => args[:sort]) @@ -79,8 +80,8 @@ module API optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID' - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], - desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], + desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' optional :source_branch, type: String, desc: 'Return merge requests with the given source branch' optional :target_branch, type: String, desc: 'Return merge requests with the given target branch' @@ -95,8 +96,8 @@ module API end params do use :merge_requests_params - optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me', - desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me', + desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' end get do authenticate! unless params[:scope] == 'all' diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 45439640ea3..74e91b02f0f 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -372,7 +372,7 @@ describe IssuesFinder do end context 'personal scope' do - let(:scope) { 'assigned-to-me' } + let(:scope) { 'assigned_to_me' } it 'returns issue assigned to the user' do expect(issues).to contain_exactly(issue1, issue2) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 90f9c4ad214..6457af77729 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -106,6 +106,15 @@ describe API::Issues do it 'returns issues assigned to me' do issue2 = create(:issue, assignees: [user2], project: project) + get api('/issues', user2), scope: 'assigned_to_me' + + expect_paginated_array_response(size: 1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues assigned to me (kebab-case)' do + issue2 = create(:issue, assignees: [user2], project: project) + get api('/issues', user2), scope: 'assigned-to-me' expect_paginated_array_response(size: 1) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index f64623d7018..1eeeb4f1045 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -34,8 +34,7 @@ describe API::MergeRequests do it 'returns an array of all merge requests' do get api('/merge_requests', user), scope: 'all' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response end it "returns authentication error without any scope" do @@ -50,11 +49,23 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(401) end + it "returns authentication error when scope is assigned_to_me" do + get api("/merge_requests"), scope: 'assigned_to_me' + + expect(response).to have_gitlab_http_status(401) + end + it "returns authentication error when scope is created-by-me" do get api("/merge_requests"), scope: 'created-by-me' expect(response).to have_gitlab_http_status(401) end + + it "returns authentication error when scope is created_by_me" do + get api("/merge_requests"), scope: 'created_by_me' + + expect(response).to have_gitlab_http_status(401) + end end context 'when authenticated' do @@ -62,27 +73,14 @@ describe API::MergeRequests do let!(:merge_request2) { create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2) } let(:user2) { create(:user) } - it 'returns an array of all merge requests' do - get api('/merge_requests', user), scope: :all - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request2.id) - end - - it 'does not return unauthorized merge requests' do + it 'returns an array of all merge requests except unauthorized ones' do private_project = create(:project, :private) merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch') get api('/merge_requests', user), scope: :all - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.map { |mr| mr['id'] }) - .not_to include(merge_request3.id) + expect_response_contain_exactly(merge_request2, merge_request_merged, merge_request_closed, merge_request) + expect(json_response.map { |mr| mr['id'] }).not_to include(merge_request3.id) end it 'returns an array of merge requests created by current user if no scope is given' do @@ -90,10 +88,7 @@ describe API::MergeRequests do get api('/merge_requests', user2) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests authored by the given user' do @@ -101,10 +96,7 @@ describe API::MergeRequests do get api('/merge_requests', user), author_id: user2.id, scope: :all - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests assigned to the given user' do @@ -112,32 +104,39 @@ describe API::MergeRequests do get api('/merge_requests', user), assignee_id: user2.id, scope: :all - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests assigned to me' do merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'assigned_to_me' + + expect_response_ordered_exactly(merge_request3) + end + + it 'returns an array of merge requests assigned to me (kebab-case)' do + merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'assigned-to-me' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns an array of merge requests created by me' do merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'created_by_me' + + expect_response_ordered_exactly(merge_request3) + end + + it 'returns an array of merge requests created by me (kebab-case)' do + merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch') + get api('/merge_requests', user2), scope: 'created-by-me' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end it 'returns merge requests reacted by the authenticated user by the given emoji' do @@ -146,19 +145,14 @@ describe API::MergeRequests do get api('/merge_requests', user2), my_reaction_emoji: award_emoji.name, scope: 'all' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request3.id) + expect_response_ordered_exactly(merge_request3) end context 'source_branch param' do it 'returns merge requests with the given source branch' do get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end @@ -166,9 +160,7 @@ describe API::MergeRequests do it 'returns merge requests with the given target branch' do get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end @@ -177,8 +169,7 @@ describe API::MergeRequests do get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end it 'returns merge requests created after a specific date' do @@ -186,8 +177,7 @@ describe API::MergeRequests do get api("/merge_requests?created_after=#{merge_request2.created_at}", user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end it 'returns merge requests updated before a specific date' do @@ -195,8 +185,7 @@ describe API::MergeRequests do get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end it 'returns merge requests updated after a specific date' do @@ -204,8 +193,7 @@ describe API::MergeRequests do get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user) - expect(json_response.size).to eq(1) - expect(json_response.first['id']).to eq(merge_request2.id) + expect_response_ordered_exactly(merge_request2) end context 'search params' do @@ -216,15 +204,13 @@ describe API::MergeRequests do it 'returns merge requests matching given search string for title' do get api("/merge_requests", user), search: merge_request.title - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request.id) + expect_response_ordered_exactly(merge_request) end it 'returns merge requests for project matching given search string for description' do get api("/merge_requests", user), project_id: project.id, search: merge_request.description - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request.id) + expect_response_ordered_exactly(merge_request) end end end @@ -235,8 +221,7 @@ describe API::MergeRequests do it 'returns merge requests for public projects' do get api("/projects/#{project.id}/merge_requests") - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response end it "returns 404 for non public projects" do @@ -265,10 +250,7 @@ describe API::MergeRequests do it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last).to have_key('web_url') expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) @@ -286,11 +268,8 @@ describe API::MergeRequests do it "returns an array of all merge_requests using simple mode" do get api("/projects/#{project.id}/merge_requests?view=simple", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers + expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at)) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) expect(json_response.last['iid']).to eq(merge_request.iid) expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last).to have_key('web_url') @@ -302,51 +281,36 @@ describe API::MergeRequests do it "returns an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_response_ordered_exactly(merge_request_merged, merge_request_closed, merge_request) expect(json_response.last['title']).to eq(merge_request.title) end it "returns an array of open merge_requests" do get api("/projects/#{project.id}/merge_requests?state=opened", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_response_ordered_exactly(merge_request) expect(json_response.last['title']).to eq(merge_request.title) end it "returns an array of closed merge_requests" do get api("/projects/#{project.id}/merge_requests?state=closed", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_response_ordered_exactly(merge_request_closed) expect(json_response.first['title']).to eq(merge_request_closed.title) end it "returns an array of merged merge_requests" do get api("/projects/#{project.id}/merge_requests?state=merged", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_response_ordered_exactly(merge_request_merged) expect(json_response.first['title']).to eq(merge_request_merged.title) end it 'returns merge_request by "iids" array' do get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid] - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_response_ordered_exactly(merge_request_closed, merge_request) expect(json_response.first['title']).to eq merge_request_closed.title - expect(json_response.first['id']).to eq merge_request_closed.id end it 'matches V4 response schema' do @@ -359,16 +323,14 @@ describe API::MergeRequests do it 'returns an empty array if no issue matches milestone' do get api("/projects/#{project.id}/merge_requests", user), milestone: '1.0.0' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end it 'returns an empty array if milestone does not exist' do get api("/projects/#{project.id}/merge_requests", user), milestone: 'foo' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end @@ -382,17 +344,13 @@ describe API::MergeRequests do it 'returns an array of merge requests matching state in milestone' do get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9', state: 'closed' - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(merge_request_closed.id) + expect_response_ordered_exactly(merge_request_closed) end it 'returns an array of labeled merge requests' do get api("/projects/#{project.id}/merge_requests?labels=#{label.title}", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label2.title, label.title]) end @@ -400,16 +358,14 @@ describe API::MergeRequests do it 'returns an array of labeled merge requests where all labels match' do get api("/projects/#{project.id}/merge_requests?labels=#{label.title},foo,bar", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end it 'returns an empty array if no merge request matches labels' do get api("/projects/#{project.id}/merge_requests?labels=foo,bar", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_paginated_array_response expect(json_response.length).to eq(0) end @@ -427,13 +383,12 @@ describe API::MergeRequests do get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(mr2.id) + expect_response_ordered_exactly(mr2) end context "with ordering" do + let(:merge_requests) { [merge_request_merged, merge_request_closed, merge_request] } + before do @mr_later = mr_with_later_created_and_updated_at_time @mr_earlier = mr_with_earlier_created_and_updated_at_time @@ -442,45 +397,25 @@ describe API::MergeRequests do it "returns an array of merge_requests in ascending order" do get api("/projects/#{project.id}/merge_requests?sort=asc", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }) end it "returns an array of merge_requests in descending order" do get api("/projects/#{project.id}/merge_requests?sort=desc", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort.reverse) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }.reverse) end it "returns an array of merge_requests ordered by updated_at" do get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['updated_at'] } - expect(response_dates).to eq(response_dates.sort.reverse) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['updated_at'] }.reverse) end it "returns an array of merge_requests ordered by created_at" do get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) - response_dates = json_response.map { |merge_request| merge_request['created_at'] } - expect(response_dates).to eq(response_dates.sort) + expect_response_ordered_exactly(*merge_requests.sort_by { |mr| mr['created_at'] }) end end @@ -488,9 +423,7 @@ describe API::MergeRequests do it 'returns merge requests with the given source branch' do get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end @@ -498,9 +431,7 @@ describe API::MergeRequests do it 'returns merge requests with the given target branch' do get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all' - expect(json_response.length).to eq(2) - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request_closed.id, merge_request_merged.id) + expect_response_contain_exactly(merge_request_closed, merge_request_merged) end end end @@ -1341,4 +1272,22 @@ describe API::MergeRequests do merge_request_closed.save merge_request_closed end + + def expect_response_contain_exactly(*items) + expect_paginated_array_response + expect(json_response.length).to eq(items.size) + expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id)) + end + + def expect_response_ordered_exactly(*items) + expect_paginated_array_response + expect(json_response.length).to eq(items.size) + expect(json_response.map { |element| element['id'] }).to eq(items.map(&:id)) + end + + def expect_paginated_array_response + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end end -- cgit v1.2.3 From c1e89492375b696b25ff63257c3cbe58ce86cc18 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 16 May 2018 21:35:24 +0900 Subject: Rescue Kubeclient::HttpError when generating prometheus_client --- app/models/clusters/applications/prometheus.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 7b25d8c4089..c702c4ee807 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -49,6 +49,11 @@ module Clusters # ensures headers containing auth data are appended to original k8s client options options = kube_client.rest_client.options.merge(headers: kube_client.headers) RestClient::Resource.new(proxy_url, options) + rescue Kubeclient::HttpError + # If users have mistakenly set parameters or removed the depended clusters, + # `proxy_url` could raise an exception because gitlab can not communicate with the cluster. + # Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab, + # we need to silence the exceptions end private -- cgit v1.2.3 From f1b43c1e4724a3e945a089fe60bb2f9a50052241 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 17 May 2018 15:47:48 +0900 Subject: Add spec for unathrozied proxy_url --- spec/features/projects/environments/environments_spec.rb | 16 ++++++++++++++++ spec/models/clusters/applications/prometheus_spec.rb | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 5248a783db4..c9be5f6ce54 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -42,6 +42,22 @@ feature 'Environments page', :js do expect(page).to have_content('You don\'t have any environments right now') end end + + context 'when cluster is not reachable' do + let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + let!(:application_prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + + before do + allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).with(anything, anything, anything, anything).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + end + + it 'should show one environment without error' do + visit_environments(project, scope: 'available') + + expect(page).to have_css('.environments-container') + expect(page.all('.environment-name').length).to eq(1) + end + end end describe 'with one stopped environment' do diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index aeca6ee903a..52404b0e247 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -85,6 +85,16 @@ describe Clusters::Applications::Prometheus do it 'copies options and headers from kube client to proxy client' do expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) end + + context 'when cluster is not reachable' do + before do + allow(kube_client).to receive(:proxy_url).with(anything, anything, anything, anything).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + end + + it 'returns nil' do + expect(subject.prometheus_client).to be_nil + end + end end end -- cgit v1.2.3 From c5bce1dc90e936eb1a1c09a66fa09471cbcf3861 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 17 May 2018 15:50:43 +0900 Subject: Add changelog --- changelogs/unreleased/fix-kube_client-proxy_url-exception.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/fix-kube_client-proxy_url-exception.yml diff --git a/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml b/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml new file mode 100644 index 00000000000..1f64ab9f30f --- /dev/null +++ b/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml @@ -0,0 +1,5 @@ +--- +title: Fix corrupted environment pages with unathorized proxy url +merge_request: 18989 +author: +type: fixed -- cgit v1.2.3 From 197d3e69d4e93067ab356db9df6df325e05fe4ef Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 21 May 2018 14:52:26 +0900 Subject: Remove unnecessary `with` from `allow` --- spec/features/projects/environments/environments_spec.rb | 2 +- spec/models/clusters/applications/prometheus_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index c9be5f6ce54..f9defa22d35 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -48,7 +48,7 @@ feature 'Environments page', :js do let!(:application_prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } before do - allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).with(anything, anything, anything, anything).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) end it 'should show one environment without error' do diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 52404b0e247..407e2fc598a 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -88,7 +88,7 @@ describe Clusters::Applications::Prometheus do context 'when cluster is not reachable' do before do - allow(kube_client).to receive(:proxy_url).with(anything, anything, anything, anything).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) + allow(kube_client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) end it 'returns nil' do -- cgit v1.2.3 From cd7702e5cd4ffcb1989d5fe758e3971a8952c5c0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 May 2018 10:07:17 +0200 Subject: Fix Rubocop offense in create pipeline services :cop: --- spec/services/ci/create_pipeline_service_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index be0255a2ccc..2b88fcc9a96 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -404,7 +404,8 @@ describe Ci::CreatePipelineService do config = YAML.dump( deploy: { environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_BUILD_ID" }, - script: 'ls' } + script: 'ls' + } ) stub_ci_pipeline_yaml_file(config) -- cgit v1.2.3 From bc535d4f885e41ec7069325bb198767770fefbb8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 21 May 2018 16:41:28 +0800 Subject: Generate the key while running the tests --- qa/qa/specs/features/project/deploy_key_clone_spec.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/qa/qa/specs/features/project/deploy_key_clone_spec.rb b/qa/qa/specs/features/project/deploy_key_clone_spec.rb index bf8fa230244..442ac312b4d 100644 --- a/qa/qa/specs/features/project/deploy_key_clone_spec.rb +++ b/qa/qa/specs/features/project/deploy_key_clone_spec.rb @@ -33,13 +33,15 @@ module QA end keys = [ - Runtime::Key::RSA.new(8192), - Runtime::Key::ECDSA.new(521), - Runtime::Key::ED25519.new + [Runtime::Key::RSA, 8192], + [Runtime::Key::ECDSA, 521], + [Runtime::Key::ED25519] ] - keys.each do |key| - scenario "user sets up a deploy key with #{key.name}(#{key.bits}) to clone code using pipelines" do + keys.each do |(key_class, bits)| + scenario "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do + key = key_class.new(*bits) + login Factory::Resource::DeployKey.fabricate! do |resource| -- cgit v1.2.3 From bc57a62a5c8c4c1e08cdf5c4a1656357d885026d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 18 May 2018 15:32:07 +0200 Subject: Improve the single-script jobs CI config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 59 ++++++++++++++++++++++++------------------------ scripts/prepare_build.sh | 2 +- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84d8e69b84e..7e65bb204c3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -126,6 +126,23 @@ stages: <<: *dedicated-no-docs-pull-cache-job <<: *except-docs-and-qa +.single-script-job: &single-script-job + image: ruby:2.4-alpine + before_script: [] + stage: build + cache: {} + dependencies: [] + variables: &single-script-job-variables + GIT_STRATEGY: none + before_script: + # We need to download the script rather than clone the repo since the + # package-and-qa job will not be able to run when the branch gets + # deleted (when merging the MR). + - export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}" + - apk add --update openssl + - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME + - chmod 755 $SCRIPT_NAME + .rake-exec: &rake-exec <<: *dedicated-no-docs-no-db-pull-cache-job script: @@ -207,19 +224,10 @@ stages: .review-docs: &review-docs <<: *dedicated-runner <<: *except-qa - image: ruby:2.4-alpine - before_script: - - gem install gitlab --no-doc - # We need to download the script rather than clone the repo since the - # review-docs-cleanup job will not be able to run when the branch gets - # deleted (when merging the MR). - - apk add --update openssl - - wget https://gitlab.com/gitlab-org/gitlab-ce/raw/master/scripts/trigger-build-docs - - chmod 755 trigger-build-docs - cache: {} - dependencies: [] + <<: *single-script-job variables: - GIT_STRATEGY: none + <<: *single-script-job-variables + SCRIPT_NAME: trigger-build-docs when: manual only: - branches @@ -253,23 +261,14 @@ stages: # Trigger a package build in omnibus-gitlab repository # package-and-qa: - image: ruby:2.4-alpine - before_script: [] - stage: build - cache: {} - when: manual + <<: *single-script-job variables: - GIT_STRATEGY: none + <<: *single-script-job-variables + SCRIPT_NAME: trigger-build-omnibus retry: 0 - before_script: - # We need to download the script rather than clone the repo since the - # package-and-qa job will not be able to run when the branch gets - # deleted (when merging the MR). - - apk add --update openssl - - wget https://gitlab.com/$CI_PROJECT_PATH/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus - - chmod 755 trigger-build-omnibus script: - - ./trigger-build-omnibus + - ./$SCRIPT_NAME + when: manual only: - //@gitlab-org/gitlab-ce - //@gitlab-org/gitlab-ee @@ -286,7 +285,8 @@ review-docs-deploy: url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX on_stop: review-docs-cleanup script: - - ./trigger-build-docs deploy + - gem install gitlab --no-ri --no-rdoc + - ./$SCRIPT_NAME deploy # Cleanup remote environment of gitlab-docs review-docs-cleanup: @@ -296,7 +296,8 @@ review-docs-cleanup: name: review-docs/$CI_COMMIT_REF_NAME action: stop script: - - ./trigger-build-docs cleanup + - gem install gitlab --no-ri --no-rdoc + - ./SCRIPT_NAME cleanup # Retrieve knapsack and rspec_flaky reports retrieve-tests-metadata: @@ -325,7 +326,7 @@ update-tests-metadata: - rspec_flaky/ policy: push script: - - retry gem install fog-aws mime-types activesupport + - retry gem install fog-aws mime-types activesupport --no-ri --no-rdoc - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH} diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index d8bcc9f8191..75a3cea0448 100644 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -11,7 +11,7 @@ fi # Only install knapsack after bundle install! Otherwise oddly some native # gems could not be found under some circumstance. No idea why, hours wasted. -retry gem install knapsack +retry gem install knapsack --no-ri --no-rdoc cp config/gitlab.yml.example config/gitlab.yml sed -i 's/bin_path: \/usr\/bin\/git/bin_path: \/usr\/local\/bin\/git/' config/gitlab.yml -- cgit v1.2.3 From 3540254e3b990431f2a4e9fcfef99daa5b0f1e13 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 21 May 2018 11:08:17 +0100 Subject: Update CHANGELOG.md for 10.7.4 [ci skip] --- CHANGELOG.md | 7 +++++++ .../xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) delete mode 100644 changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 29047c3ad65..86f12a75b7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.7.4 (2018-05-21) + +### Fixed (1 change) + +- Fix error when deleting an empty list of refs. + + ## 10.7.3 (2018-05-02) ### Fixed (8 changes) diff --git a/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml b/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml deleted file mode 100644 index 94da4d74300..00000000000 --- a/changelogs/unreleased/xeodon-gitlab-ce-fix-45743-master-fix-gitaly-delete-refs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix error when deleting an empty list of refs -merge_request: -author: -type: fixed -- cgit v1.2.3 From 93349b4440add0da70f87026ae434be5c10f1ad1 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 15 May 2018 22:34:29 +0900 Subject: Append trace only if the job is running --- lib/api/runner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 649feba1036..a4df98cadac 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -165,7 +165,7 @@ module API body_start = content_range[0].to_i body_end = body_start + body_data.bytesize - stream_size = job.trace.append(body_data, body_start) + stream_size = job.trace.append(body_data, body_start) if job.running? unless stream_size == body_end break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" }) end -- cgit v1.2.3 From 5af5815d62f3430c52bb489c590eaa32402b18fa Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 18 May 2018 16:05:11 +0900 Subject: Add the same gurad clause with artifacts uplaoding --- lib/api/runner.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index a4df98cadac..a7f1cb1131f 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -149,6 +149,7 @@ module API end patch '/:id/trace' do job = authenticate_job! + forbidden!('Job is not running') unless job.running? error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') content_range = request.headers['Content-Range'] @@ -165,7 +166,7 @@ module API body_start = content_range[0].to_i body_end = body_start + body_data.bytesize - stream_size = job.trace.append(body_data, body_start) if job.running? + stream_size = job.trace.append(body_data, body_start) unless stream_size == body_end break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" }) end -- cgit v1.2.3 From e4ebee4246ec389ec52e0d9453db338ce2993084 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Mon, 21 May 2018 10:48:59 +0000 Subject: Remove experiemental notes from Prometheus docs --- doc/administration/monitoring/prometheus/gitlab_metrics.md | 4 ++-- doc/administration/monitoring/prometheus/index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 69600cad25c..411a0fae93f 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -1,7 +1,7 @@ # GitLab Prometheus metrics >**Note:** -Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For +Available since [Omnibus GitLab 9.3][29118]. For installations from source you'll have to configure it yourself. To enable the GitLab Prometheus metrics: @@ -24,7 +24,7 @@ server, because the embedded server configuration is overwritten once every ## Metrics available -In this experimental phase, only a few metrics are available: +The following metrics are available: | Metric | Type | Since | Description | |:--------------------------------- |:--------- |:----- |:----------- | diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 3d24812c66a..f47add48345 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -120,7 +120,7 @@ To disable the monitoring of Kubernetes: ## GitLab Prometheus metrics -> Introduced as an experimental feature in GitLab 9.3. +> Introduced in GitLab 9.3. GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic. -- cgit v1.2.3 From b053c483e792e00c50ba57e461e3a09ebdb8f34d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 21 May 2018 21:11:00 +0900 Subject: Add test for the cancelled jobs --- spec/requests/api/runner_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index da392c5ab81..efb9bddde44 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -918,6 +918,22 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(job.reload.trace.raw).to eq 'BUILD TRACE appended appended' end + context 'when job is cancelled' do + before do + job.cancel + end + + context 'when trace is patched' do + before do + patch_the_trace + end + + it 'returns Forbidden ' do + expect(response.status).to eq(403) + end + end + end + context 'when redis data are flushed' do before do redis_shared_state_cleanup! -- cgit v1.2.3 From c94b607ae6150808a063fc6b2916bda0dc4850ab Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 21 May 2018 21:42:01 +0900 Subject: Add changelog --- changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml diff --git a/changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml b/changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml new file mode 100644 index 00000000000..f32c70cf884 --- /dev/null +++ b/changelogs/unreleased/create-live-trace-only-if-job-is-complete.yml @@ -0,0 +1,5 @@ +--- +title: Forbid to patch traces for finished jobs +merge_request: 18969 +author: +type: fixed -- cgit v1.2.3