From 94e0ca004959196d0c8481f8f8eb28084ae021ae Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Jul 2016 12:42:37 +0100 Subject: Allow GFM autocomplete to be trigger without the preceding space Closes #19975 --- app/assets/javascripts/gfm_auto_complete.js.coffee | 6 ++++++ spec/features/issues/gfm_autocomplete_spec.rb | 24 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 spec/features/issues/gfm_autocomplete_spec.rb diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 4a851d9c9fb..31c6a3b4d5e 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -84,6 +84,7 @@ GitLab.GfmAutoComplete = @Loading.template insertTpl: ':${name}:' data: ['loading'] + startWithSpace: false callbacks: sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter @@ -100,6 +101,7 @@ GitLab.GfmAutoComplete = insertTpl: '${atwho-at}${username}' searchKey: 'search' data: ['loading'] + startWithSpace: false callbacks: sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter @@ -126,6 +128,7 @@ GitLab.GfmAutoComplete = @Loading.template data: ['loading'] insertTpl: '${atwho-at}${id}' + startWithSpace: false callbacks: sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter @@ -149,6 +152,7 @@ GitLab.GfmAutoComplete = @Loading.template insertTpl: '${atwho-at}"${title}"' data: ['loading'] + startWithSpace: false callbacks: beforeSave: (milestones) -> $.map milestones, (m) -> @@ -169,6 +173,7 @@ GitLab.GfmAutoComplete = @Loading.template data: ['loading'] insertTpl: '${atwho-at}${id}' + startWithSpace: false callbacks: sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter @@ -187,6 +192,7 @@ GitLab.GfmAutoComplete = searchKey: 'search' displayTpl: @Labels.template insertTpl: '${atwho-at}${title}' + startWithSpace: false callbacks: beforeSave: (merges) -> sanitizeLabelTitle = (title)-> diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb new file mode 100644 index 00000000000..000b190de77 --- /dev/null +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +feature 'GFM autocomplete', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + + before do + project.team << [user, :master] + login_as(user) + visit namespace_project_issue_path(project.namespace, project, issue) + end + + it 'opens autocomplete menu when doesnt starts with space' do + sleep 2 + + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('testing') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-view') + end +end -- cgit v1.2.1 From 45fa7fd4ddf35314602168cd869ee4a67c44250b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Jul 2016 14:41:27 +0100 Subject: Correctly checks for character before GFM input char It must not be letter or number to work --- app/assets/javascripts/gfm_auto_complete.js.coffee | 17 +++++++++++++++ spec/features/issues/gfm_autocomplete_spec.rb | 24 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 31c6a3b4d5e..8ef4641cf81 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -43,6 +43,17 @@ GitLab.GfmAutoComplete = @at else value + matcher: (flag, subtext, should_startWithSpace) -> + # escape RegExp + flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + + # À + _a = decodeURI("%C3%80") + # ÿ + _y = decodeURI("%C3%BF") + regexp = new RegExp "(?:\\B|\\W|\\s)#{flag}([A-Za-z#{_a}-#{_y}0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi' + match = regexp.exec subtext + if match then match[2] || match[1] else null # Add GFM auto-completion to all input fields, that accept GFM input. setup: (wrap) -> @@ -89,6 +100,7 @@ GitLab.GfmAutoComplete = sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter beforeInsert: @DefaultOptions.beforeInsert + matcher: @DefaultOptions.matcher # Team Members @input.atwho @@ -106,6 +118,7 @@ GitLab.GfmAutoComplete = sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter beforeInsert: @DefaultOptions.beforeInsert + matcher: @DefaultOptions.matcher beforeSave: (members) -> $.map members, (m) -> return m if not m.username? @@ -133,6 +146,7 @@ GitLab.GfmAutoComplete = sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter beforeInsert: @DefaultOptions.beforeInsert + matcher: @DefaultOptions.matcher beforeSave: (issues) -> $.map issues, (i) -> return i if not i.title? @@ -154,6 +168,7 @@ GitLab.GfmAutoComplete = data: ['loading'] startWithSpace: false callbacks: + matcher: @DefaultOptions.matcher beforeSave: (milestones) -> $.map milestones, (m) -> return m if not m.title? @@ -178,6 +193,7 @@ GitLab.GfmAutoComplete = sorter: @DefaultOptions.sorter filter: @DefaultOptions.filter beforeInsert: @DefaultOptions.beforeInsert + matcher: @DefaultOptions.matcher beforeSave: (merges) -> $.map merges, (m) -> return m if not m.title? @@ -194,6 +210,7 @@ GitLab.GfmAutoComplete = insertTpl: '${atwho-at}${title}' startWithSpace: false callbacks: + matcher: @DefaultOptions.matcher beforeSave: (merges) -> sanitizeLabelTitle = (title)-> if /[\w\?&]+\s+[\w\?&]+/g.test(title) diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 000b190de77..7e602672d30 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -9,16 +9,34 @@ feature 'GFM autocomplete', feature: true, js: true do project.team << [user, :master] login_as(user) visit namespace_project_issue_path(project.namespace, project, issue) - end - it 'opens autocomplete menu when doesnt starts with space' do sleep 2 + end + it 'opens autocomplete menu when field starts with text' do page.within '.timeline-content-form' do - find('#note_note').native.send_keys('testing') + find('#note_note').native.send_keys('') find('#note_note').native.send_keys('@') end expect(page).to have_selector('.atwho-view') end + + it 'opens autocomplete menu when field is prefixed with non-text character' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-view') + end + + it 'doesnt open autocomplete menu character is prefixed with text' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('testing') + find('#note_note').native.send_keys('@') + end + + expect(page).not_to have_selector('.atwho-view') + end end -- cgit v1.2.1 From 03a235783f697572fe201332cb82746401a01daf Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 14 Nov 2016 10:34:10 +0000 Subject: Tests fix --- spec/features/issues/gfm_autocomplete_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index c0d093fb155..c421da97d76 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -20,7 +20,7 @@ feature 'GFM autocomplete', feature: true, js: true do find('#note_note').native.send_keys('@') end - expect(page).to have_selector('.atwho-view') + expect(page).to have_selector('.atwho-container') end it 'opens autocomplete menu when field is prefixed with non-text character' do @@ -29,7 +29,7 @@ feature 'GFM autocomplete', feature: true, js: true do find('#note_note').native.send_keys('@') end - expect(page).to have_selector('.atwho-view') + expect(page).to have_selector('.atwho-container') end it 'doesnt open autocomplete menu character is prefixed with text' do -- cgit v1.2.1 From d97ad73c82e5299fba893cc9b273eced8ec37d3e Mon Sep 17 00:00:00 2001 From: Ryan O'Boyle Date: Wed, 9 Nov 2016 03:35:07 -0500 Subject: Fix typos in API doc examples --- doc/api/build_triggers.md | 8 ++++---- doc/api/build_variables.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md index 1b7a1840138..b6459971420 100644 --- a/doc/api/build_triggers.md +++ b/doc/api/build_triggers.md @@ -15,7 +15,7 @@ GET /projects/:id/triggers | `id` | integer | yes | The ID of a project | ``` -curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers" ``` ```json @@ -51,7 +51,7 @@ GET /projects/:id/triggers/:token | `token` | string | yes | The `token` of a trigger | ``` -curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" ``` ```json @@ -77,7 +77,7 @@ POST /projects/:id/triggers | `id` | integer | yes | The ID of a project | ``` -curl --request POST --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers" ``` ```json @@ -104,7 +104,7 @@ DELETE /projects/:id/triggers/:token | `token` | string | yes | The `token` of a trigger | ``` -curl --request DELETE --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" ``` ```json diff --git a/doc/api/build_variables.md b/doc/api/build_variables.md index a21751a49ea..917e9773913 100644 --- a/doc/api/build_variables.md +++ b/doc/api/build_variables.md @@ -13,7 +13,7 @@ GET /projects/:id/variables | `id` | integer | yes | The ID of a project | ``` -curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" ``` ```json @@ -43,7 +43,7 @@ GET /projects/:id/variables/:key | `key` | string | yes | The `key` of a variable | ``` -curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/TEST_VARIABLE_1" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/TEST_VARIABLE_1" ``` ```json @@ -68,7 +68,7 @@ POST /projects/:id/variables | `value` | string | yes | The `value` of a variable | ``` -curl --request POST --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value" ``` ```json @@ -93,7 +93,7 @@ PUT /projects/:id/variables/:key | `value` | string | yes | The `value` of a variable | ``` -curl --request PUT --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/NEW_VARIABLE" --form "value=updated value" +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/NEW_VARIABLE" --form "value=updated value" ``` ```json @@ -117,7 +117,7 @@ DELETE /projects/:id/variables/:key | `key` | string | yes | The `key` of a variable | ``` -curl --request DELETE --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/VARIABLE_1" +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/VARIABLE_1" ``` ```json -- cgit v1.2.1 From 6d1c5761cd520f3cb7fa4dbb1a1937c29f468884 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 17 Nov 2016 00:57:50 +0800 Subject: Improve how we could cancel pipelines: * Introduce `HasStatus.cancelable` which we might be able to cancel * Cancel and check upon `cancelable` * Also cancel on `CommitStatus` rather than just `Ci::Build` Fixes #23635 Fixes #17845 --- app/models/ci/pipeline.rb | 4 +-- app/models/concerns/has_status.rb | 4 +++ changelogs/unreleased/fix-cancelling-pipelines.yml | 4 +++ spec/features/projects/pipelines_spec.rb | 4 +-- spec/models/ci/pipeline_spec.rb | 40 ++++++++++++++++++++++ 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/fix-cancelling-pipelines.yml diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 3fee6c18770..4eb85f62ee4 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -167,11 +167,11 @@ module Ci end def cancelable? - builds.running_or_pending.any? + statuses.cancelable.any? end def cancel_running - builds.running_or_pending.each(&:cancel) + statuses.cancelable.each(&:cancel) end def retry_failed(user) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index ef3e73a4072..1332743429d 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -73,6 +73,10 @@ module HasStatus scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } + + scope :cancelable, -> do + where(status: [:running, :pending, :created]) + end end def started? diff --git a/changelogs/unreleased/fix-cancelling-pipelines.yml b/changelogs/unreleased/fix-cancelling-pipelines.yml new file mode 100644 index 00000000000..c21e663093a --- /dev/null +++ b/changelogs/unreleased/fix-cancelling-pipelines.yml @@ -0,0 +1,4 @@ +--- +title: Fix cancelling created or external pipelines +merge_request: 7508 +author: diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index db56a50e058..b3ef9a11d56 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -90,8 +90,8 @@ describe "Pipelines" do visit namespace_project_pipelines_path(project.namespace, project) end - it 'is not cancelable' do - expect(page).not_to have_link('Cancel') + it 'is cancelable' do + expect(page).to have_link('Cancel') end it 'has pipeline running' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 71b7628ef10..d013dc7b31a 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -402,6 +402,46 @@ describe Ci::Pipeline, models: true do end end + describe '#cancelable?' do + subject { pipeline.cancelable? } + + %i[created running pending].each do |status| + context "when there is a build #{status}" do + before do + create(:ci_build, status, pipeline: pipeline) + end + + it { is_expected.to be_truthy } + end + + context "when there is an external job #{status}" do + before do + create(:generic_commit_status, status, pipeline: pipeline) + end + + it { is_expected.to be_truthy } + end + end + + %i[success failed canceled].each do |status| + context "when there is a build #{status}" do + before do + create(:ci_build, status, pipeline: pipeline) + end + + it { is_expected.to be_falsey } + end + + context "when there is an external job #{status}" do + before do + create(:generic_commit_status, status, pipeline: pipeline) + end + + it { is_expected.to be_falsey } + end + end + end + describe '#execute_hooks' do let!(:build_a) { create_build('a', 0) } let!(:build_b) { create_build('b', 1) } -- cgit v1.2.1 From 9a0201473e24e5036506f0cc8761290da1ca743b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 18 Nov 2016 23:16:51 +0800 Subject: Add when cancelling for external jobs, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18622182 --- spec/features/projects/pipelines_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index b3ef9a11d56..544372108ed 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -97,6 +97,13 @@ describe "Pipelines" do it 'has pipeline running' do expect(page).to have_selector('.ci-running') end + + context 'when canceling' do + before { click_link('Cancel') } + + it { expect(page).not_to have_link('Cancel') } + it { expect(page).to have_selector('.ci-canceled') } + end end context 'when failed' do -- cgit v1.2.1 From 100076ecbbdf3eae361a6356ddfb55b1694e4741 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 18 Nov 2016 23:27:06 +0800 Subject: Add tests against two jobs having different status Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18622469 --- spec/models/ci/pipeline_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index d013dc7b31a..2cc6d1be606 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -421,6 +421,18 @@ describe Ci::Pipeline, models: true do it { is_expected.to be_truthy } end + + %i[success failed canceled].each do |status2| + context "when there are two builds for #{status} and #{status2}" do + before do + build = %i[ci_build generic_commit_status] + create(build.sample, status, pipeline: pipeline) + create(build.sample, status2, pipeline: pipeline) + end + + it { is_expected.to be_truthy } + end + end end %i[success failed canceled].each do |status| -- cgit v1.2.1 From b6a7a4783435a7fa34f26dbf3b16ab8e7ed21b88 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 19 Nov 2016 01:02:49 +0800 Subject: Add a lot of tests for scopes, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18622499 --- spec/factories/ci/builds.rb | 4 ++ spec/factories/commit_statuses.rb | 4 ++ spec/models/concerns/has_status_spec.rb | 77 +++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 0c93bbdfe26..e7fe489e5eb 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -38,6 +38,10 @@ FactoryGirl.define do status 'canceled' end + trait :skipped do + status 'skipped' + end + trait :running do status 'running' end diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index 995f2080f10..756b341ecba 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -19,6 +19,10 @@ FactoryGirl.define do status 'canceled' end + trait :skipped do + status 'skipped' + end + trait :running do status 'running' end diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 87bffbdc54e..24cd435256e 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -123,4 +123,81 @@ describe HasStatus do it_behaves_like 'build status summary' end end + + def self.random_type + %i[ci_build generic_commit_status].sample + end + + context 'for scope with one status' do + shared_examples 'having a job' do |type, status| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + describe ".#{status}" do + subject { CommitStatus.public_send(status).all } + + it { is_expected.to contain_exactly(job) } + end + + describe '.relevant' do + subject { CommitStatus.relevant.all } + + it do + case status + when :created + is_expected.to be_empty + else + is_expected.to contain_exactly(job) + end + end + end + end + end + + %i[created running pending success + failed canceled skipped].each do |status| + it_behaves_like 'having a job', random_type, status + end + end + + context 'for scope with more statuses' do + shared_examples 'having a job' do |type, status, excluded_status| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it do + case status + when excluded_status + is_expected.to be_empty + else + is_expected.to contain_exactly(job) + end + end + end + end + + describe '.running_or_pending' do + subject { CommitStatus.running_or_pending } + + %i[running pending created].each do |status| + it_behaves_like 'having a job', random_type, status, :created + end + end + + describe '.finished' do + subject { CommitStatus.finished } + + %i[success failed canceled created].each do |status| + it_behaves_like 'having a job', random_type, status, :created + end + end + + describe '.cancelable' do + subject { CommitStatus.cancelable } + + %i[running pending created failed].each do |status| + it_behaves_like 'having a job', random_type, status, :failed + end + end + end end -- cgit v1.2.1 From 92a83aaee1c5715bf95715649aa8ced50a70dea8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 19 Nov 2016 01:17:40 +0800 Subject: Add a test for Ci::Pipeline#cancel_running: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18622559 I didn't write an exhaustive one because we already have it on HasStatus, from: https://gitlab.com/gitlab-org/gitlab-ce/commit/b6a7a4783435a7fa34f26dbf3b16ab8e7ed21b88 --- spec/models/ci/pipeline_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2cc6d1be606..74579e0c832 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -454,6 +454,22 @@ describe Ci::Pipeline, models: true do end end + describe '#cancel_running' do + context 'when there is a running external job and created build' do + before do + create(:generic_commit_status, :running, pipeline: pipeline) + create(:ci_build, :created, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(pipeline.statuses.pluck(:status)). + to contain_exactly('canceled', 'canceled') + end + end + end + describe '#execute_hooks' do let!(:build_a) { create_build('a', 0) } let!(:build_b) { create_build('b', 1) } -- cgit v1.2.1 From ca639c9b824d6c8effb620bc71255eb0895ab2cc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 19 Nov 2016 14:04:11 +0100 Subject: Allow to retry failed or canceled builds and fix cancel running specs failure --- app/models/ci/pipeline.rb | 14 +++++---- app/models/concerns/has_status.rb | 1 + spec/models/ci/pipeline_spec.rb | 63 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4eb85f62ee4..c0f2c8ba787 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -161,9 +161,7 @@ module Ci end def retryable? - builds.latest.any? do |build| - (build.failed? || build.canceled?) && build.retryable? - end + builds.latest.failed_or_canceled.any?(&:retryable?) end def cancelable? @@ -171,12 +169,16 @@ module Ci end def cancel_running - statuses.cancelable.each(&:cancel) + Gitlab::OptimisticLocking.retry_lock(statuses.cancelable) do |cancelable| + cancelable.each(&:cancel) + end end def retry_failed(user) - builds.latest.failed.select(&:retryable?).each do |build| - Ci::Build.retry(build, user) + Gitlab::OptimisticLocking.retry_lock(builds.latest.failed_or_canceled) do |failed| + failed.select(&:retryable?).each do |build| + Ci::Build.retry(build, user) + end end end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 1332743429d..2f5aa91a964 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -73,6 +73,7 @@ module HasStatus scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } + scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } scope :cancelable, -> do where(status: [:running, :pending, :created]) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 74579e0c832..af619a02ed9 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -455,17 +455,74 @@ describe Ci::Pipeline, models: true do end describe '#cancel_running' do + let(:latest_status) { pipeline.statuses.pluck(:status) } + context 'when there is a running external job and created build' do before do + create(:ci_build, :running, pipeline: pipeline) create(:generic_commit_status, :running, pipeline: pipeline) - create(:ci_build, :created, pipeline: pipeline) pipeline.cancel_running end it 'cancels both jobs' do - expect(pipeline.statuses.pluck(:status)). - to contain_exactly('canceled', 'canceled') + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :running, stage_idx: 0, pipeline: pipeline) + create(:ci_build, :running, stage_idx: 1, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + end + + describe '#retry_failed' do + let(:latest_status) { pipeline.statuses.latest.pluck(:status) } + + context 'when there is a failed build and failed external status' do + before do + create(:ci_build, :failed, name: 'build', pipeline: pipeline) + create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline) + + pipeline.retry_failed(nil) + end + + it 'retries only build' do + expect(latest_status).to contain_exactly('pending', 'failed') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(nil) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') + end + end + + context 'when there are canceled and failed' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(nil) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') end end end -- cgit v1.2.1 From c4ded595ccf520bc30bde90403366ad14ba8b594 Mon Sep 17 00:00:00 2001 From: David Wagner Date: Fri, 18 Nov 2016 12:57:25 +0100 Subject: Fix broken external links in help/index.html An external link was recently added but was broken because 'https://gitlab.com/help/' was prepended to every link in the page. Since no link in the main help readme begins with "help" and since doing so wouldn't make sense, the substitution conditionaly prepending "help" can be simplified and reused. Signed-off-by: David Wagner --- app/controllers/help_controller.rb | 6 +++--- spec/controllers/help_controller_spec.rb | 14 +++----------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 4b3c71874be..a10cdcce72b 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -6,9 +6,9 @@ class HelpController < ApplicationController def index @help_index = File.read(Rails.root.join('doc', 'README.md')) - # Prefix Markdown links with `help/` unless they already have been - # See http://rubular.com/r/ie2MlpdUMq - @help_index.gsub!(/(\]\()(\/?help\/)?([^\)\(]+\))/, '\1/help/\3') + # Prefix Markdown links with `help/` unless they are external links + # See http://rubular.com/r/MioSrVLK3S + @help_index.gsub!(%r{(\]\()(?!.+://)([^\)\(]+\))}, '\1/help/\2') end def show diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index 6fc6ea95e13..cffed987f6b 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -16,14 +16,6 @@ describe HelpController do end end - context 'when url prefixed with help/' do - it 'will be an absolute path' do - stub_readme("[API](help/api/README.md)") - get :index - expect(assigns[:help_index]).to eq '[API](/help/api/README.md)' - end - end - context 'when url prefixed with help' do it 'will be an absolute path' do stub_readme("[API](helpful_hints/README.md)") @@ -32,11 +24,11 @@ describe HelpController do end end - context 'when url prefixed with /help/' do + context 'when url is an external link' do it 'will not be changed' do - stub_readme("[API](/help/api/README.md)") + stub_readme("[external](https://some.external.link)") get :index - expect(assigns[:help_index]).to eq '[API](/help/api/README.md)' + expect(assigns[:help_index]).to eq '[external](https://some.external.link)' end end end -- cgit v1.2.1 From f50fdacae4fa1b6313ec9c36541749678267a082 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 15 Nov 2016 22:57:28 +0100 Subject: Upgrade grape-entity Fixes gitlab-org/gitlab-ce#14329 --- Gemfile | 2 +- Gemfile.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 9e815925a1f..14ea7ec7ea0 100644 --- a/Gemfile +++ b/Gemfile @@ -68,7 +68,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist' # API gem 'grape', '~> 0.15.0' -gem 'grape-entity', '~> 0.4.2' +gem 'grape-entity', '~> 0.5.2' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' # Pagination diff --git a/Gemfile.lock b/Gemfile.lock index bdc60552480..331ffd89148 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -316,8 +316,7 @@ GEM rack-accept rack-mount virtus (>= 1.0.0) - grape-entity (0.4.8) - activesupport + grape-entity (0.5.2) multi_json (>= 1.3.2) haml (4.0.7) tilt @@ -869,7 +868,7 @@ DEPENDENCIES gollum-rugged_adapter (~> 0.4.2) gon (~> 6.1.0) grape (~> 0.15.0) - grape-entity (~> 0.4.2) + grape-entity (~> 0.5.2) haml_lint (~> 0.18.2) hamlit (~> 2.6.1) health_check (~> 2.2.0) -- cgit v1.2.1 From 2282a3bd67d32555517d150c1e471d714e51d410 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 15 Nov 2016 23:37:22 +0100 Subject: Add changelog entry for grape-entity upgrade [ci skip] --- changelogs/unreleased/zj-upgrade-grape.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/zj-upgrade-grape.yml diff --git a/changelogs/unreleased/zj-upgrade-grape.yml b/changelogs/unreleased/zj-upgrade-grape.yml new file mode 100644 index 00000000000..75a72283d1e --- /dev/null +++ b/changelogs/unreleased/zj-upgrade-grape.yml @@ -0,0 +1,4 @@ +--- +title: Update grape entity to 0.5.2 +merge_request: 7491 +author: -- cgit v1.2.1 From 01f238893aaf8f5aa24eec7e33edc479d4e1315d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 27 Oct 2016 14:04:43 +0200 Subject: Rename MWBS service to Merge When Pipeline Succeeds --- .../projects/merge_requests_controller.rb | 13 +- .../merge_when_build_succeeds_service.rb | 45 ------ .../merge_when_pipeline_succeeds_service.rb | 45 ++++++ app/workers/pipeline_success_worker.rb | 2 +- lib/api/merge_requests.rb | 14 +- .../projects/merge_requests_controller_spec.rb | 4 +- .../merge_when_build_succeeds_service_spec.rb | 163 -------------------- .../merge_when_pipeline_succeeds_service_spec.rb | 169 +++++++++++++++++++++ spec/workers/pipeline_success_worker_spec.rb | 2 +- 9 files changed, 237 insertions(+), 220 deletions(-) delete mode 100644 app/services/merge_requests/merge_when_build_succeeds_service.rb create mode 100644 app/services/merge_requests/merge_when_pipeline_succeeds_service.rb delete mode 100644 spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb create mode 100644 spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 036fde87619..5a2136303c9 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -302,9 +302,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def cancel_merge_when_build_succeeds - return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + return access_denied! + end - MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request) + MergeRequests::MergeWhenPipelineSucceedsService + .new(@project, current_user) + .cancel(@merge_request) end def merge @@ -331,8 +335,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController end if @merge_request.pipeline.active? - MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) - .execute(@merge_request) + MergeRequests::MergeWhenPipelineSucceedsService + .new(@project, current_user, merge_params) + .execute(@merge_request) @status = :merge_when_build_succeeds elsif @merge_request.pipeline.success? # This can be triggered when a user clicks the auto merge button while diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb deleted file mode 100644 index dc159de0058..00000000000 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ /dev/null @@ -1,45 +0,0 @@ -module MergeRequests - class MergeWhenBuildSucceedsService < MergeRequests::BaseService - # Marks the passed `merge_request` to be merged when the build succeeds or - # updates the params for the automatic merge - def execute(merge_request) - merge_request.merge_params.merge!(params) - - # The service is also called when the merge params are updated. - already_approved = merge_request.merge_when_build_succeeds? - - unless already_approved - merge_request.merge_when_build_succeeds = true - merge_request.merge_user = @current_user - - SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit) - end - - merge_request.save - end - - # Triggers the automatic merge of merge_request once the pipeline succeeds - def trigger(pipeline) - return unless pipeline.success? - - pipeline_merge_requests(pipeline) do |merge_request| - next unless merge_request.merge_when_build_succeeds? - next unless merge_request.mergeable? - - MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) - end - end - - # Cancels the automatic merge - def cancel(merge_request) - if merge_request.merge_when_build_succeeds? && merge_request.open? - merge_request.reset_merge_when_build_succeeds - SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user) - - success - else - error("Can't cancel the automatic merge", 406) - end - end - end -end diff --git a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb new file mode 100644 index 00000000000..5616edf8b4a --- /dev/null +++ b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb @@ -0,0 +1,45 @@ +module MergeRequests + class MergeWhenPipelineSucceedsService < MergeRequests::BaseService + # Marks the passed `merge_request` to be merged when the build succeeds or + # updates the params for the automatic merge + def execute(merge_request) + merge_request.merge_params.merge!(params) + + # The service is also called when the merge params are updated. + already_approved = merge_request.merge_when_build_succeeds? + + unless already_approved + merge_request.merge_when_build_succeeds = true + merge_request.merge_user = @current_user + + SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit) + end + + merge_request.save + end + + # Triggers the automatic merge of merge_request once the pipeline succeeds + def trigger(pipeline) + return unless pipeline.success? + + pipeline_merge_requests(pipeline) do |merge_request| + next unless merge_request.merge_when_build_succeeds? + next unless merge_request.mergeable? + + MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) + end + end + + # Cancels the automatic merge + def cancel(merge_request) + if merge_request.merge_when_build_succeeds? && merge_request.open? + merge_request.reset_merge_when_build_succeeds + SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user) + + success + else + error("Can't cancel the automatic merge", 406) + end + end + end +end diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb index 2aa6fff24da..cc0eb708cf9 100644 --- a/app/workers/pipeline_success_worker.rb +++ b/app/workers/pipeline_success_worker.rb @@ -4,7 +4,7 @@ class PipelineSuccessWorker def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| - MergeRequests::MergeWhenBuildSucceedsService + MergeRequests::MergeWhenPipelineSucceedsService .new(pipeline.project, nil) .trigger(pipeline) end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4176c7eec06..8c55f09a9ce 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -205,11 +205,13 @@ module API } if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? - ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request) + ::MergeRequests::MergeWhenPipelineSucceedsService + .new(merge_request.target_project, current_user, merge_params) + .execute(merge_request) else - ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request) + ::MergeRequests::MergeService + .new(merge_request.target_project, current_user, merge_params) + .execute(merge_request) end present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project @@ -223,7 +225,9 @@ module API unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) - ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) + ::MergeRequest::MergeWhenPipelineSucceedsService + .new(merge_request.target_project, current_user) + .cancel(merge_request) end desc 'Get the comments of a merge request' do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 1d0750d1719..9e0b80205d8 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -292,7 +292,9 @@ describe Projects::MergeRequestsController do it 'sets the MR to merge when the build succeeds' do service = double(:merge_when_build_succeeds_service) - expect(MergeRequests::MergeWhenBuildSucceedsService).to receive(:new).with(project, anything, anything).and_return(service) + expect(MergeRequests::MergeWhenPipelineSucceedsService) + .to receive(:new).with(project, anything, anything) + .and_return(service) expect(service).to receive(:execute).with(merge_request) merge_when_build_succeeds diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb deleted file mode 100644 index 1f90efdbd6a..00000000000 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ /dev/null @@ -1,163 +0,0 @@ -require 'spec_helper' - -describe MergeRequests::MergeWhenBuildSucceedsService do - let(:user) { create(:user) } - let(:project) { create(:project) } - - let(:mr_merge_if_green_enabled) do - create(:merge_request, merge_when_build_succeeds: true, merge_user: user, - source_branch: "master", target_branch: 'feature', - source_project: project, target_project: project, state: "opened") - end - - let(:pipeline) { create(:ci_pipeline_with_one_job, ref: mr_merge_if_green_enabled.source_branch, project: project) } - let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') } - - describe "#execute" do - let(:merge_request) do - create(:merge_request, target_project: project, source_project: project, - source_branch: "feature", target_branch: 'master') - end - - context 'first time enabling' do - before do - allow(merge_request).to receive(:pipeline).and_return(pipeline) - service.execute(merge_request) - end - - it 'sets the params, merge_user, and flag' do - expect(merge_request).to be_valid - expect(merge_request.merge_when_build_succeeds).to be_truthy - expect(merge_request.merge_params).to eq commit_message: 'Awesome message' - expect(merge_request.merge_user).to be user - end - - it 'creates a system note' do - note = merge_request.notes.last - expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/ - end - end - - context 'already approved' do - let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, new_key: true) } - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } - - before do - allow(mr_merge_if_green_enabled).to receive(:pipeline).and_return(pipeline) - allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true) - allow(pipeline).to receive(:success?).and_return(true) - end - - it 'updates the merge params' do - expect(SystemNoteService).not_to receive(:merge_when_build_succeeds) - - service.execute(mr_merge_if_green_enabled) - expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) - end - end - end - - describe "#trigger" do - let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } - let(:merge_request_head) do - project.commit(mr_merge_if_green_enabled.source_branch).id - end - - context 'when triggered by pipeline with valid ref and sha' do - let(:triggering_pipeline) do - create(:ci_pipeline, project: project, ref: merge_request_ref, - sha: merge_request_head, status: 'success') - end - - it "merges all merge requests with merge when build succeeds enabled" do - expect(MergeWorker).to receive(:perform_async) - service.trigger(triggering_pipeline) - end - end - - context 'when triggered by an old pipeline' do - let(:old_pipeline) do - create(:ci_pipeline, project: project, ref: merge_request_ref, - sha: '1234abcdef', status: 'success') - end - - it 'it does not merge merge request' do - expect(MergeWorker).not_to receive(:perform_async) - service.trigger(old_pipeline) - end - end - - context 'when triggered by pipeline from a different branch' do - let(:unrelated_pipeline) do - create(:ci_pipeline, project: project, ref: 'feature', - sha: merge_request_head, status: 'success') - end - - it 'does not merge request' do - expect(MergeWorker).not_to receive(:perform_async) - service.trigger(unrelated_pipeline) - end - end - end - - describe "#cancel" do - before do - service.cancel(mr_merge_if_green_enabled) - end - - it "resets all the merge_when_build_succeeds params" do - expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey - expect(mr_merge_if_green_enabled.merge_params).to eq({}) - expect(mr_merge_if_green_enabled.merge_user).to be nil - end - - it 'Posts a system note' do - note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'Canceled the automatic merge' - end - end - - describe 'pipeline integration' do - context 'when there are multiple stages in the pipeline' do - let(:ref) { mr_merge_if_green_enabled.source_branch } - let(:sha) { project.commit(ref).id } - - let(:pipeline) do - create(:ci_empty_pipeline, ref: ref, sha: sha, project: project) - end - - let!(:build) do - create(:ci_build, :created, pipeline: pipeline, ref: ref, - name: 'build', stage: 'build') - end - - let!(:test) do - create(:ci_build, :created, pipeline: pipeline, ref: ref, - name: 'test', stage: 'test') - end - - before do - # This behavior of MergeRequest: we instantiate a new object - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do - Ci::Pipeline.find(pipeline.id) - end - end - - it "doesn't merge if any of stages failed" do - expect(MergeWorker).not_to receive(:perform_async) - - build.success - test.reload - test.drop - end - - it 'merges when all stages succeeded' do - expect(MergeWorker).to receive(:perform_async) - - build.success - test.reload - test.success - end - end - end -end diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb new file mode 100644 index 00000000000..793806bd69a --- /dev/null +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -0,0 +1,169 @@ +require 'spec_helper' + +describe MergeRequests::MergeWhenPipelineSucceedsService do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:mr_merge_if_green_enabled) do + create(:merge_request, merge_when_build_succeeds: true, merge_user: user, + source_branch: "master", target_branch: 'feature', + source_project: project, target_project: project, state: "opened") + end + + let(:pipeline) do + create(:ci_pipeline_with_one_job, ref: mr_merge_if_green_enabled.source_branch, + project: project) + end + + let(:service) do + described_class.new(project, user, commit_message: 'Awesome message') + end + + describe "#execute" do + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project, + source_branch: "feature", target_branch: 'master') + end + + context 'first time enabling' do + before do + allow(merge_request).to receive(:pipeline).and_return(pipeline) + service.execute(merge_request) + end + + it 'sets the params, merge_user, and flag' do + expect(merge_request).to be_valid + expect(merge_request.merge_when_build_succeeds).to be_truthy + expect(merge_request.merge_params).to eq commit_message: 'Awesome message' + expect(merge_request.merge_user).to be user + end + + it 'creates a system note' do + note = merge_request.notes.last + expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/ + end + end + + context 'already approved' do + let(:service) { described_class.new(project, user, new_key: true) } + let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } + + before do + allow(mr_merge_if_green_enabled).to receive(:pipeline).and_return(pipeline) + allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true) + allow(pipeline).to receive(:success?).and_return(true) + end + + it 'updates the merge params' do + expect(SystemNoteService).not_to receive(:merge_when_build_succeeds) + + service.execute(mr_merge_if_green_enabled) + expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key) + end + end + end + + describe "#trigger" do + let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } + let(:merge_request_head) do + project.commit(mr_merge_if_green_enabled.source_branch).id + end + + context 'when triggered by pipeline with valid ref and sha' do + let(:triggering_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: merge_request_head, status: 'success') + end + + it "merges all merge requests with merge when build succeeds enabled" do + expect(MergeWorker).to receive(:perform_async) + service.trigger(triggering_pipeline) + end + end + + context 'when triggered by an old pipeline' do + let(:old_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: '1234abcdef', status: 'success') + end + + it 'it does not merge merge request' do + expect(MergeWorker).not_to receive(:perform_async) + service.trigger(old_pipeline) + end + end + + context 'when triggered by pipeline from a different branch' do + let(:unrelated_pipeline) do + create(:ci_pipeline, project: project, ref: 'feature', + sha: merge_request_head, status: 'success') + end + + it 'does not merge request' do + expect(MergeWorker).not_to receive(:perform_async) + service.trigger(unrelated_pipeline) + end + end + end + + describe "#cancel" do + before do + service.cancel(mr_merge_if_green_enabled) + end + + it "resets all the merge_when_build_succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey + expect(mr_merge_if_green_enabled.merge_params).to eq({}) + expect(mr_merge_if_green_enabled.merge_user).to be nil + end + + it 'Posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'Canceled the automatic merge' + end + end + + describe 'pipeline integration' do + context 'when there are multiple stages in the pipeline' do + let(:ref) { mr_merge_if_green_enabled.source_branch } + let(:sha) { project.commit(ref).id } + + let(:pipeline) do + create(:ci_empty_pipeline, ref: ref, sha: sha, project: project) + end + + let!(:build) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'build', stage: 'build') + end + + let!(:test) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'test', stage: 'test') + end + + before do + # This behavior of MergeRequest: we instantiate a new object + allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do + Ci::Pipeline.find(pipeline.id) + end + end + + it "doesn't merge if any of stages failed" do + expect(MergeWorker).not_to receive(:perform_async) + + build.success + test.reload + test.drop + end + + it 'merges when all stages succeeded' do + expect(MergeWorker).to receive(:perform_async) + + build.success + test.reload + test.success + end + end + end +end diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb index 5e31cc2c8e7..d1c84adda6f 100644 --- a/spec/workers/pipeline_success_worker_spec.rb +++ b/spec/workers/pipeline_success_worker_spec.rb @@ -7,7 +7,7 @@ describe PipelineSuccessWorker do it 'performs "merge when pipeline succeeds"' do expect_any_instance_of( - MergeRequests::MergeWhenBuildSucceedsService + MergeRequests::MergeWhenPipelineSucceedsService ).to receive(:trigger) described_class.new.perform(pipeline.id) -- cgit v1.2.1 From bd3ae192bb62d522912281b5943073f6ad444fe4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 Nov 2016 10:55:54 +0100 Subject: Rename MWPS in system notes and related tests --- app/services/system_note_service.rb | 2 +- .../merge_requests/widget/open/_merge_when_build_succeeds.html.haml | 2 +- spec/features/merge_requests/merge_when_build_succeeds_spec.rb | 4 ++-- .../merge_requests/merge_when_pipeline_succeeds_service_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1ce66d50368..925e4938784 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -135,7 +135,7 @@ module SystemNoteService # Called when 'merge when build succeeds' is executed def merge_when_build_succeeds(noteable, project, author, last_commit) - body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" + body = "Enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index 2b6b5e05e86..cea3e28ceb3 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -1,6 +1,6 @@ %h4 Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)} - to be merged automatically when the build succeeds. + to be merged automatically when the pipeline succeeds. %div %p = succeed '.' do diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 8eceaad2457..a710ca66f70 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -40,11 +40,11 @@ feature 'Merge When Build Succeeds', feature: true, js: true do it 'activates Merge When Build Succeeds feature' do expect(page).to have_link "Cancel Automatic Merge" - expect(page).to have_content "Set by #{user.name} to be merged automatically when the build succeeds." + expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." expect(page).to have_content "The source branch will not be removed." visit_merge_request(merge_request) # Needed to refresh the page - expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i + expect(page).to have_content /Enabled an automatic merge when the pipeline for [0-9a-f]{8} succeeds/i end end end diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 793806bd69a..3b7c9204a35 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -40,7 +40,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do it 'creates a system note' do note = merge_request.notes.last - expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/ + expect(note.note).to match /Enabled an automatic merge when the pipeline for (\w+\/\w+@)?[0-9a-z]{8}/ end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 56d39e9a005..7f0da4bf03c 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -225,8 +225,8 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' - it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-f]{40} succeeds/ + it "posts the 'merge when pipeline succeeds' system note" do + expect(subject.note).to match /Enabled an automatic merge when the pipeline for (\w+\/\w+@)?[0-9a-f]{40} succeeds/ end end @@ -239,7 +239,7 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' - it "posts the Merge When Build Succeeds system note" do + it "posts the 'merge when pipeline succeeds' system note" do expect(subject.note).to eq "Canceled the automatic merge" end end -- cgit v1.2.1 From d07ef089c8870b4a5a1c69555c54c93ff1f1fa24 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 Nov 2016 11:20:53 +0100 Subject: Rename MWPS in user interface and feature tests --- .../merge_requests/widget/open/_accept.html.haml | 4 +- .../merge_when_build_succeeds_spec.rb | 108 -------------------- .../merge_when_pipeline_succeeds_spec.rb | 109 +++++++++++++++++++++ 3 files changed, 111 insertions(+), 110 deletions(-) delete mode 100644 spec/features/merge_requests/merge_when_build_succeeds_spec.rb create mode 100644 spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index ce43ca3a286..435fe835fae 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -9,7 +9,7 @@ - if @pipeline && @pipeline.active? %span.btn-group = button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do - Merge When Build Succeeds + Merge When Pipeline Succeeds - unless @project.only_allow_merge_if_build_succeeds? = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do = icon('caret-down') @@ -19,7 +19,7 @@ %li = link_to "#", class: "merge_when_build_succeeds" do = icon('check fw') - Merge When Build Succeeds + Merge When Pipeline Succeeds %li = link_to "#", class: "accept_merge_request" do = icon('warning fw') diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb deleted file mode 100644 index a710ca66f70..00000000000 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ /dev/null @@ -1,108 +0,0 @@ -require 'spec_helper' - -feature 'Merge When Build Succeeds', feature: true, js: true do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - - let(:merge_request) do - create(:merge_request_with_diffs, source_project: project, - author: user, - title: 'Bug NS-04') - end - - let(:pipeline) do - create(:ci_pipeline, project: project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch) - end - - before { project.team << [user, :master] } - - context 'when there is active build for merge request' do - background do - create(:ci_build, pipeline: pipeline) - end - - before do - login_as user - visit_merge_request(merge_request) - end - - it 'displays the Merge When Build Succeeds button' do - expect(page).to have_button "Merge When Build Succeeds" - end - - context "Merge When Build succeeds enabled" do - before do - click_button "Merge When Build Succeeds" - end - - it 'activates Merge When Build Succeeds feature' do - expect(page).to have_link "Cancel Automatic Merge" - - expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." - expect(page).to have_content "The source branch will not be removed." - - visit_merge_request(merge_request) # Needed to refresh the page - expect(page).to have_content /Enabled an automatic merge when the pipeline for [0-9a-f]{8} succeeds/i - end - end - end - - context 'when merge when build succeeds is enabled' do - let(:merge_request) do - create(:merge_request_with_diffs, :simple, source_project: project, - author: user, - merge_user: user, - title: 'MepMep', - merge_when_build_succeeds: true) - end - - let!(:build) do - create(:ci_build, pipeline: pipeline) - end - - before do - login_as user - visit_merge_request(merge_request) - end - - it 'allows to cancel the automatic merge' do - click_link "Cancel Automatic Merge" - - expect(page).to have_button "Merge When Build Succeeds" - - visit_merge_request(merge_request) # refresh the page - expect(page).to have_content "canceled the automatic merge" - end - - it "allows the user to remove the source branch" do - expect(page).to have_link "Remove Source Branch When Merged" - - click_link "Remove Source Branch When Merged" - expect(page).to have_content "The source branch will be removed" - end - - context 'when build succeeds' do - background { build.success } - - it 'merges merge request' do - visit_merge_request(merge_request) # refresh the page - - expect(page).to have_content 'The changes were merged' - expect(merge_request.reload).to be_merged - end - end - end - - context 'when build is not active' do - it "does not allow to enable merge when build succeeds" do - visit_merge_request(merge_request) - expect(page).not_to have_link "Merge When Build Succeeds" - end - end - - def visit_merge_request(merge_request) - visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) - end -end diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb new file mode 100644 index 00000000000..638b27172ec --- /dev/null +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' + +feature 'Merge When Pipeline Succeeds', :feature, :js do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, + author: user, + title: 'Bug NS-04') + end + + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) + end + + before { project.team << [user, :master] } + + context 'when there is active pipeline for merge request' do + background do + create(:ci_build, pipeline: pipeline) + end + + before do + login_as user + visit_merge_request(merge_request) + end + + it 'displays the Merge When Pipeline Succeeds button' do + expect(page).to have_button "Merge When Pipeline Succeeds" + end + + context "Merge When Pipeline Succeeds enabled" do + before do + click_button "Merge When Pipeline Succeeds" + end + + it 'activates Merge When Pipeline Succeeds feature' do + expect(page).to have_link "Cancel Automatic Merge" + + expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds." + expect(page).to have_content "The source branch will not be removed." + + visit_merge_request(merge_request) # Needed to refresh the page + expect(page).to have_content /Enabled an automatic merge when the pipeline for [0-9a-f]{8} succeeds/i + end + end + end + + context 'when merge when pipeline succeeds is enabled' do + let(:merge_request) do + create(:merge_request_with_diffs, :simple, source_project: project, + author: user, + merge_user: user, + title: 'MepMep', + merge_when_build_succeeds: true) + end + + let!(:build) do + create(:ci_build, pipeline: pipeline) + end + + before do + login_as user + visit_merge_request(merge_request) + end + + it 'allows to cancel the automatic merge' do + click_link "Cancel Automatic Merge" + + expect(page).to have_button "Merge When Pipeline Succeeds" + + visit_merge_request(merge_request) # refresh the page + expect(page).to have_content "canceled the automatic merge" + end + + it "allows the user to remove the source branch" do + expect(page).to have_link "Remove Source Branch When Merged" + + click_link "Remove Source Branch When Merged" + expect(page).to have_content "The source branch will be removed" + end + + context 'when pipeline succeeds' do + background { build.success } + + it 'merges merge request' do + visit_merge_request(merge_request) # refresh the page + + expect(page).to have_content 'The changes were merged' + expect(merge_request.reload).to be_merged + end + end + end + + context 'when pipeline is not active' do + it "does not allow to enable merge when pipeline succeeds" do + visit_merge_request(merge_request) + + expect(page).not_to have_link 'Merge When Pipeline Succeeds' + end + end + + def visit_merge_request(merge_request) + visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) + end +end -- cgit v1.2.1 From c6a4f9fc5b98024d4af872b0009b90c36bc2e595 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 Nov 2016 11:27:28 +0100 Subject: Update some docs to reflect MWPS name change --- app/services/system_note_service.rb | 4 ++-- doc/api/merge_requests.md | 2 +- doc/development/code_review.md | 4 ++-- doc/intro/README.md | 2 +- doc/user/project/merge_requests.md | 10 +++++----- .../project/merge_requests/merge_when_build_succeeds.md | 16 ++++++++-------- doc/workflow/README.md | 2 +- lib/api/merge_requests.rb | 4 ++-- spec/models/merge_request_spec.rb | 2 +- spec/requests/api/merge_requests_spec.rb | 2 +- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 925e4938784..5bf6e094d68 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -133,14 +133,14 @@ module SystemNoteService create_note(noteable: noteable, project: project, author: author, note: body) end - # Called when 'merge when build succeeds' is executed + # Called when 'merge when pipeline succeeds' is executed def merge_when_build_succeeds(noteable, project, author, last_commit) body = "Enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end - # Called when 'merge when build succeeds' is canceled + # Called when 'merge when pipeline succeeds' is canceled def cancel_merge_when_build_succeeds(noteable, project, author) body = 'Canceled the automatic merge' diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index f4167403c2c..75b9d5d1a75 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -518,7 +518,7 @@ Parameters: } ``` -## Cancel Merge When Build Succeeds +## Cancel Merge When Pipeline Succeeds If successful you'll get `200 OK`. diff --git a/doc/development/code_review.md b/doc/development/code_review.md index c5c23b5c0b8..4bf0e7197e9 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -70,8 +70,8 @@ experience, refactors the existing code). Then: - After a round of line notes, it can be helpful to post a summary note such as "LGTM :thumbsup:", or "Just a couple things to address." - Avoid accepting a merge request before the build succeeds. Of course, "Merge - When Build Succeeds" (MWBS) is fine. -- If you set the MR to "Merge When Build Succeeds", you should take over + When Pipeline Succeeds" (MWPS) is fine. +- If you set the MR to "Merge When Pipeline Succeeds", you should take over subsequent revisions for anything that would be spotted after that. ## Credits diff --git a/doc/intro/README.md b/doc/intro/README.md index 1790b2b761f..6deed35b7c9 100644 --- a/doc/intro/README.md +++ b/doc/intro/README.md @@ -23,7 +23,7 @@ Create merge requests and review code. - [Fork a project and contribute to it](../workflow/forking_workflow.md) - [Create a new merge request](../gitlab-basics/add-merge-request.md) - [Automatically close issues from merge requests](../user/project/issues/automatic_issue_closing.md) -- [Automatically merge when your builds succeed](../user/project/merge_requests/merge_when_build_succeeds.md) +- [Automatically merge when pipeline succeeds](../user/project/merge_requests/merge_when_build_succeeds.md) - [Revert any commit](../user/project/merge_requests/revert_changes.md) - [Cherry-pick any commit](../user/project/merge_requests/cherry_pick_changes.md) diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md index 5af9a5d049c..e76428d41f3 100644 --- a/doc/user/project/merge_requests.md +++ b/doc/user/project/merge_requests.md @@ -19,14 +19,14 @@ in a merged merge requests or a commit. [Learn more about cherry-picking changes.](merge_requests/cherry_pick_changes.md) -## Merge when build succeeds +## Merge when pipeline succeeds When reviewing a merge request that looks ready to merge but still has one or -more CI builds running, you can set it to be merged automatically when all -builds succeed. This way, you don't have to wait for the builds to finish and -remember to merge the request manually. +more CI builds running, you can set it to be merged automatically when CI +pipeline succeeds. This way, you don't have to wait for the pipeline to finish +and remember to merge the request manually. -[Learn more about merging when build succeeds.](merge_requests/merge_when_build_succeeds.md) +[Learn more about merging when pipeline succeeds.](merge_requests/merge_when_build_succeeds.md) ## Resolve discussion comments in merge requests reviews diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md index d4e5b5de685..75ad18b28cf 100644 --- a/doc/user/project/merge_requests/merge_when_build_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -1,13 +1,13 @@ -# Merge When Build Succeeds +# Merge When Pipeline Succeeds When reviewing a merge request that looks ready to merge but still has one or more CI builds running, you can set it to be merged automatically when the -builds pipeline succeed. This way, you don't have to wait for the builds to +builds pipeline succeeds. This way, you don't have to wait for the builds to finish and remember to merge the request manually. ![Enable](img/merge_when_build_succeeds_enable.png) -When you hit the "Merge When Build Succeeds" button, the status of the merge +When you hit the "Merge When Pipeline Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait for the pipeline to succeed and want to merge immediately, this option is available in the dropdown menu on the right of the main button. @@ -27,20 +27,20 @@ will automatically be merged after all. When the merge request is updated with new commits, the automatic merge is automatically canceled to allow the new changes to be reviewed. -## Only allow merge requests to be merged if the build succeeds +## Only allow merge requests to be merged if the pipeline succeeds > **Note:** You need to have builds configured to enable this feature. -You can prevent merge requests from being merged if their build did not succeed. +You can prevent merge requests from being merged if their pipeline did not succeed. Navigate to your project's settings page, select the -**Only allow merge requests to be merged if the build succeeds** check box and +**Only allow merge requests to be merged if the pipeline succeeds** check box and hit **Save** for the changes to take effect. -![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) +![Only allow merge if pipeline succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) From now on, every time the pipeline fails you will not be able to merge the merge request from the UI, until you make all relevant builds pass. -![Only allow merge if build succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png) +![Only allow merge if pipeline succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png) diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 2d9bfbc0629..57a0d21c7a7 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -25,7 +25,7 @@ - [Merge Requests](../user/project/merge_requests.md) - [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md) - [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md) - - [Merge when build succeeds](../user/project/merge_requests/merge_when_build_succeeds.md) + - [Merge when pipeline succeeds](../user/project/merge_requests/merge_when_build_succeeds.md) - [Resolve discussion comments in merge requests reviews](../user/project/merge_requests/merge_request_discussion_resolution.md) - [Resolve merge conflicts in the UI](../user/project/merge_requests/resolve_conflicts.md) - [Revert changes in the UI](../user/project/merge_requests/revert_changes.md) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 8c55f09a9ce..19f93c1c892 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -181,7 +181,7 @@ module API optional :should_remove_source_branch, type: Boolean, desc: 'When true, the source branch will be deleted if possible' optional :merge_when_build_succeeds, type: Boolean, - desc: 'When true, this merge request will be merged when the build succeeds' + desc: 'When true, this merge request will be merged when the pipeline succeeds' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' end put "#{path}/merge" do @@ -217,7 +217,7 @@ module API present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end - desc 'Cancel merge if "Merge when build succeeds" is enabled' do + desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do success Entities::MergeRequest end post "#{path}/cancel_merge_when_build_succeeds" do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index fb032a89d50..e295a920b45 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -31,7 +31,7 @@ describe MergeRequest, models: true do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } - context "Validation of merge user with Merge When Build succeeds" do + context "Validation of merge user with Merge When Pipeline Succeeds" do it "allows user to be nil when the feature is disabled" do expect(subject).to be_valid end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 7b3d1460c90..212e633c9aa 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -463,7 +463,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) end - it "enables merge when build succeeds if the ci is active" do + it "enables merge when pipeline succeeds if the pipeline is active" do allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) -- cgit v1.2.1 From 1edb1746a51a19fae24c976c329e80a1dbd6062a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 21 Nov 2016 17:59:57 +0800 Subject: Prefer a description for it and split the case: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18730091 --- spec/models/ci/pipeline_spec.rb | 22 ++++++++---- spec/models/concerns/has_status_spec.rb | 61 +++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index af619a02ed9..29e5693d5ab 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -403,15 +403,15 @@ describe Ci::Pipeline, models: true do end describe '#cancelable?' do - subject { pipeline.cancelable? } - %i[created running pending].each do |status| context "when there is a build #{status}" do before do create(:ci_build, status, pipeline: pipeline) end - it { is_expected.to be_truthy } + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end end context "when there is an external job #{status}" do @@ -419,7 +419,9 @@ describe Ci::Pipeline, models: true do create(:generic_commit_status, status, pipeline: pipeline) end - it { is_expected.to be_truthy } + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end end %i[success failed canceled].each do |status2| @@ -430,7 +432,9 @@ describe Ci::Pipeline, models: true do create(build.sample, status2, pipeline: pipeline) end - it { is_expected.to be_truthy } + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end end end end @@ -441,7 +445,9 @@ describe Ci::Pipeline, models: true do create(:ci_build, status, pipeline: pipeline) end - it { is_expected.to be_falsey } + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end end context "when there is an external job #{status}" do @@ -449,7 +455,9 @@ describe Ci::Pipeline, models: true do create(:generic_commit_status, status, pipeline: pipeline) end - it { is_expected.to be_falsey } + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end end end end diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 24cd435256e..788c84bf5de 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -134,20 +134,20 @@ describe HasStatus do let!(:job) { create(type, status) } describe ".#{status}" do - subject { CommitStatus.public_send(status).all } - - it { is_expected.to contain_exactly(job) } + it 'contains the job' do + expect(CommitStatus.public_send(status).all). + to contain_exactly(job) + end end describe '.relevant' do - subject { CommitStatus.relevant.all } - - it do - case status - when :created - is_expected.to be_empty - else - is_expected.to contain_exactly(job) + if status == :created + it 'contains nothing' do + expect(CommitStatus.relevant.all).to be_empty + end + else + it 'contains the job' do + expect(CommitStatus.relevant.all).to contain_exactly(job) end end end @@ -161,17 +161,22 @@ describe HasStatus do end context 'for scope with more statuses' do - shared_examples 'having a job' do |type, status, excluded_status| + shared_examples 'containing the job' do |type, status| context "when it's #{status} #{type} job" do let!(:job) { create(type, status) } - it do - case status - when excluded_status - is_expected.to be_empty - else - is_expected.to contain_exactly(job) - end + it 'contains the job' do + is_expected.to contain_exactly(job) + end + end + end + + shared_examples 'not containing the job' do |type, status| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it 'contains nothing' do + is_expected.to be_empty end end end @@ -179,25 +184,31 @@ describe HasStatus do describe '.running_or_pending' do subject { CommitStatus.running_or_pending } - %i[running pending created].each do |status| - it_behaves_like 'having a job', random_type, status, :created + %i[running pending].each do |status| + it_behaves_like 'containing the job', random_type, status end + + it_behaves_like 'not containing the job', random_type, :created end describe '.finished' do subject { CommitStatus.finished } - %i[success failed canceled created].each do |status| - it_behaves_like 'having a job', random_type, status, :created + %i[success failed canceled].each do |status| + it_behaves_like 'containing the job', random_type, status end + + it_behaves_like 'not containing the job', random_type, :created end describe '.cancelable' do subject { CommitStatus.cancelable } - %i[running pending created failed].each do |status| - it_behaves_like 'having a job', random_type, status, :failed + %i[running pending created].each do |status| + it_behaves_like 'containing the job', random_type, status end + + it_behaves_like 'not containing the job', random_type, :failed end end end -- cgit v1.2.1 From 0a5a65df0c7d08e3ce041e10906549313a9ad156 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 Nov 2016 12:42:44 +0100 Subject: Add Changelog entry for Merge When Pipeline Succeeds --- .../unreleased/fix-rename-mwbs-to-merge-when-pipeline-succeeds.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-rename-mwbs-to-merge-when-pipeline-succeeds.yml diff --git a/changelogs/unreleased/fix-rename-mwbs-to-merge-when-pipeline-succeeds.yml b/changelogs/unreleased/fix-rename-mwbs-to-merge-when-pipeline-succeeds.yml new file mode 100644 index 00000000000..f8acc6ef8ad --- /dev/null +++ b/changelogs/unreleased/fix-rename-mwbs-to-merge-when-pipeline-succeeds.yml @@ -0,0 +1,4 @@ +--- +title: Rename Merge When Build Succeeds to Merge When Pipeline Succeeds +merge_request: 7135 +author: -- cgit v1.2.1 From d09d6ad01da722b3558565b8b78f0476b529a91c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 21 Nov 2016 22:39:58 +0800 Subject: Test against all types and more status: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18748231 --- spec/models/concerns/has_status_spec.rb | 82 ++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 788c84bf5de..9defb17dc92 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -124,30 +124,28 @@ describe HasStatus do end end - def self.random_type - %i[ci_build generic_commit_status].sample - end - context 'for scope with one status' do - shared_examples 'having a job' do |type, status| - context "when it's #{status} #{type} job" do - let!(:job) { create(type, status) } + shared_examples 'having a job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } - describe ".#{status}" do - it 'contains the job' do - expect(CommitStatus.public_send(status).all). - to contain_exactly(job) + describe ".#{status}" do + it 'contains the job' do + expect(CommitStatus.public_send(status).all). + to contain_exactly(job) + end end - end - describe '.relevant' do - if status == :created - it 'contains nothing' do - expect(CommitStatus.relevant.all).to be_empty - end - else - it 'contains the job' do - expect(CommitStatus.relevant.all).to contain_exactly(job) + describe '.relevant' do + if status == :created + it 'contains nothing' do + expect(CommitStatus.relevant.all).to be_empty + end + else + it 'contains the job' do + expect(CommitStatus.relevant.all).to contain_exactly(job) + end end end end @@ -156,27 +154,31 @@ describe HasStatus do %i[created running pending success failed canceled skipped].each do |status| - it_behaves_like 'having a job', random_type, status + it_behaves_like 'having a job', status end end context 'for scope with more statuses' do - shared_examples 'containing the job' do |type, status| - context "when it's #{status} #{type} job" do - let!(:job) { create(type, status) } + shared_examples 'containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } - it 'contains the job' do - is_expected.to contain_exactly(job) + it 'contains the job' do + is_expected.to contain_exactly(job) + end end end end - shared_examples 'not containing the job' do |type, status| - context "when it's #{status} #{type} job" do - let!(:job) { create(type, status) } + shared_examples 'not containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } - it 'contains nothing' do - is_expected.to be_empty + it 'contains nothing' do + is_expected.to be_empty + end end end end @@ -185,30 +187,36 @@ describe HasStatus do subject { CommitStatus.running_or_pending } %i[running pending].each do |status| - it_behaves_like 'containing the job', random_type, status + it_behaves_like 'containing the job', status end - it_behaves_like 'not containing the job', random_type, :created + %i[created failed success].each do |status| + it_behaves_like 'not containing the job', status + end end describe '.finished' do subject { CommitStatus.finished } %i[success failed canceled].each do |status| - it_behaves_like 'containing the job', random_type, status + it_behaves_like 'containing the job', status end - it_behaves_like 'not containing the job', random_type, :created + %i[created running pending].each do |status| + it_behaves_like 'not containing the job', status + end end describe '.cancelable' do subject { CommitStatus.cancelable } %i[running pending created].each do |status| - it_behaves_like 'containing the job', random_type, status + it_behaves_like 'containing the job', status end - it_behaves_like 'not containing the job', random_type, :failed + %i[failed success skipped canceled].each do |status| + it_behaves_like 'not containing the job', status + end end end end -- cgit v1.2.1 From c7c4850d0b9d6751a5f6fdaa1f9c34aee6728676 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 22 Nov 2016 01:21:15 +0800 Subject: Test against all possible cases, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18755739 --- spec/models/ci/pipeline_spec.rb | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 29e5693d5ab..cbf25c23b05 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -403,10 +403,10 @@ describe Ci::Pipeline, models: true do end describe '#cancelable?' do - %i[created running pending].each do |status| - context "when there is a build #{status}" do + %i[created running pending].each do |status0| + context "when there is a build #{status0}" do before do - create(:ci_build, status, pipeline: pipeline) + create(:ci_build, status0, pipeline: pipeline) end it 'is cancelable' do @@ -414,9 +414,9 @@ describe Ci::Pipeline, models: true do end end - context "when there is an external job #{status}" do + context "when there is an external job #{status0}" do before do - create(:generic_commit_status, status, pipeline: pipeline) + create(:generic_commit_status, status0, pipeline: pipeline) end it 'is cancelable' do @@ -424,16 +424,19 @@ describe Ci::Pipeline, models: true do end end - %i[success failed canceled].each do |status2| - context "when there are two builds for #{status} and #{status2}" do - before do - build = %i[ci_build generic_commit_status] - create(build.sample, status, pipeline: pipeline) - create(build.sample, status2, pipeline: pipeline) - end + %i[success failed canceled].each do |status1| + %i[ci_build generic_commit_status].each do |type0| + %i[ci_build generic_commit_status].each do |type1| + context "when there are #{type0} and #{type1} for #{status0} and #{status1}" do + before do + create(type0, status0, pipeline: pipeline) + create(type1, status1, pipeline: pipeline) + end - it 'is cancelable' do - expect(pipeline.cancelable?).to be_truthy + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end end end end -- cgit v1.2.1 From 3566965417b7921cdb301c44cfb308551cdc1e82 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 22 Nov 2016 18:55:00 +0800 Subject: Passing a user to retry_failed in tests Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18794547 --- spec/models/ci/pipeline_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index cbf25c23b05..03924a436de 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -503,7 +503,7 @@ describe Ci::Pipeline, models: true do create(:ci_build, :failed, name: 'build', pipeline: pipeline) create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline) - pipeline.retry_failed(nil) + pipeline.retry_failed(create(:user)) end it 'retries only build' do @@ -516,7 +516,7 @@ describe Ci::Pipeline, models: true do create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline) - pipeline.retry_failed(nil) + pipeline.retry_failed(create(:user)) end it 'retries both builds' do @@ -529,7 +529,7 @@ describe Ci::Pipeline, models: true do create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline) - pipeline.retry_failed(nil) + pipeline.retry_failed(create(:user)) end it 'retries both builds' do -- cgit v1.2.1 From cc43c9acc29117cd9ec3c25805e6c5ea874e5969 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 22 Nov 2016 19:07:12 +0800 Subject: Expand the loop and reduce overlapped conditions Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18794681 --- spec/models/ci/pipeline_spec.rb | 42 +++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 03924a436de..438810d8206 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -425,18 +425,36 @@ describe Ci::Pipeline, models: true do end %i[success failed canceled].each do |status1| - %i[ci_build generic_commit_status].each do |type0| - %i[ci_build generic_commit_status].each do |type1| - context "when there are #{type0} and #{type1} for #{status0} and #{status1}" do - before do - create(type0, status0, pipeline: pipeline) - create(type1, status1, pipeline: pipeline) - end - - it 'is cancelable' do - expect(pipeline.cancelable?).to be_truthy - end - end + context "when there are generic_commit_status jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:generic_commit_status, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are generic_commit_status and ci_build jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are ci_build jobs for #{status0} and #{status1}" do + before do + create(:ci_build, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy end end end -- cgit v1.2.1 From a085e3fb97c0ae9ebfd61f5045a28a5809252e45 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 22 Nov 2016 11:47:26 +0000 Subject: Fixed resolved discussion timeago not rendering Closes #24787 --- app/assets/javascripts/diff_notes/models/discussion.js.es6 | 9 ++++++--- changelogs/unreleased/resolve-discussions-timeago.yml | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/resolve-discussions-timeago.yml diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6 index 439f55520ef..badcdccc840 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6 @@ -57,14 +57,17 @@ class DiscussionModel { } updateHeadline (data) { - const $discussionHeadline = $(`.discussion[data-discussion-id="${this.id}"] .js-discussion-headline`); + const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`; + const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`); if (data.discussion_headline_html) { if ($discussionHeadline.length) { $discussionHeadline.replaceWith(data.discussion_headline_html); } else { - $(`.discussion[data-discussion-id="${this.id}"] .discussion-header`).append(data.discussion_headline_html); + $(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html); } + + gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`)); } else { $discussionHeadline.remove(); } @@ -74,7 +77,7 @@ class DiscussionModel { if (!this.canResolve) { return false; } - + for (const noteId in this.notes) { const note = this.notes[noteId]; diff --git a/changelogs/unreleased/resolve-discussions-timeago.yml b/changelogs/unreleased/resolve-discussions-timeago.yml new file mode 100644 index 00000000000..ffedeb93f1d --- /dev/null +++ b/changelogs/unreleased/resolve-discussions-timeago.yml @@ -0,0 +1,4 @@ +--- +title: Fixed timeago not rendering when resolving a discussion +merge_request: +author: -- cgit v1.2.1 From daf26fa0db66de0f3ef121ba362c5073c7f183e1 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sun, 20 Nov 2016 13:42:58 +0500 Subject: Refactor create service spec before: 1 minute 11.81 seconds after: 52.47 seconds --- .../unreleased/refactor-create-service-spec.yml | 4 + spec/services/projects/create_service_spec.rb | 212 ++++++++++----------- 2 files changed, 110 insertions(+), 106 deletions(-) create mode 100644 changelogs/unreleased/refactor-create-service-spec.yml diff --git a/changelogs/unreleased/refactor-create-service-spec.yml b/changelogs/unreleased/refactor-create-service-spec.yml new file mode 100644 index 00000000000..148a0fee02c --- /dev/null +++ b/changelogs/unreleased/refactor-create-service-spec.yml @@ -0,0 +1,4 @@ +--- +title: Refactor create service spec +merge_request: 7609 +author: Semyon Pupkov diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index fbd22560d6e..a1539b69401 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,150 +1,150 @@ require 'spec_helper' -describe Projects::CreateService, services: true do - describe :create_by_user do - before do - @user = create :user - @opts = { - name: "GitLab", - namespace: @user.namespace - } - end +describe Projects::CreateService, '#execute', services: true do + let(:user) { create :user } + let(:opts) do + { + name: "GitLab", + namespace: user.namespace + } + end - it 'creates labels on Project creation if there are templates' do - Label.create(title: "bug", template: true) - project = create_project(@user, @opts) - project.reload + it 'creates labels on Project creation if there are templates' do + Label.create(title: "bug", template: true) + project = create_project(user, opts) - expect(project.labels).not_to be_empty - end + expect(project.labels).not_to be_empty + end - context 'user namespace' do - before do - @project = create_project(@user, @opts) - end + context 'user namespace' do + it do + project = create_project(user, opts) - it { expect(@project).to be_valid } - it { expect(@project.owner).to eq(@user) } - it { expect(@project.team.masters).to include(@user) } - it { expect(@project.namespace).to eq(@user.namespace) } + expect(project).to be_valid + expect(project.owner).to eq(user) + expect(project.team.masters).to include(user) + expect(project.namespace).to eq(user.namespace) end + end - context 'group namespace' do - before do - @group = create :group - @group.add_owner(@user) + context 'group namespace' do + let(:group) do + create(:group).tap do |group| + group.add_owner(user) + end + end - @user.refresh_authorized_projects # Ensure cache is warm + before do + user.refresh_authorized_projects # Ensure cache is warm + end - @opts.merge!(namespace_id: @group.id) - @project = create_project(@user, @opts) - end + it do + project = create_project(user, opts.merge!(namespace_id: group.id)) - it { expect(@project).to be_valid } - it { expect(@project.owner).to eq(@group) } - it { expect(@project.namespace).to eq(@group) } - it { expect(@user.authorized_projects).to include(@project) } + expect(project).to be_valid + expect(project.owner).to eq(group) + expect(project.namespace).to eq(group) + expect(user.authorized_projects).to include(project) end + end - context 'error handling' do - it 'handles invalid options' do - @opts.merge!({ default_branch: 'master' } ) - expect(create_project(@user, @opts)).to eq(nil) - end + context 'error handling' do + it 'handles invalid options' do + opts.merge!({ default_branch: 'master' } ) + expect(create_project(user, opts)).to eq(nil) end + end - context 'wiki_enabled creates repository directory' do - context 'wiki_enabled true creates wiki repository directory' do - before do - @project = create_project(@user, @opts) - @path = ProjectWiki.new(@project, @user).send(:path_to_repo) - end + context 'wiki_enabled creates repository directory' do + context 'wiki_enabled true creates wiki repository directory' do + it do + project = create_project(user, opts) + path = ProjectWiki.new(project, user).send(:path_to_repo) - it { expect(File.exist?(@path)).to be_truthy } + expect(File.exist?(path)).to be_truthy end + end - context 'wiki_enabled false does not create wiki repository directory' do - before do - @opts.merge!(wiki_enabled: false) - @project = create_project(@user, @opts) - @path = ProjectWiki.new(@project, @user).send(:path_to_repo) - end + context 'wiki_enabled false does not create wiki repository directory' do + it do + opts.merge!(wiki_enabled: false) + project = create_project(user, opts) + path = ProjectWiki.new(project, user).send(:path_to_repo) - it { expect(File.exist?(@path)).to be_falsey } + expect(File.exist?(path)).to be_falsey end end + end - context 'builds_enabled global setting' do - let(:project) { create_project(@user, @opts) } - - subject { project.builds_enabled? } + context 'builds_enabled global setting' do + let(:project) { create_project(user, opts) } - context 'global builds_enabled false does not enable CI by default' do - before do - project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) - end + subject { project.builds_enabled? } - it { is_expected.to be_falsey } + context 'global builds_enabled false does not enable CI by default' do + before do + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) end - context 'global builds_enabled true does enable CI by default' do - before do - project.project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) - end + it { is_expected.to be_falsey } + end - it { is_expected.to be_truthy } + context 'global builds_enabled true does enable CI by default' do + before do + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) end + + it { is_expected.to be_truthy } end + end - context 'restricted visibility level' do - before do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) + context 'restricted visibility level' do + before do + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - @opts.merge!( - visibility_level: Gitlab::VisibilityLevel.options['Public'] - ) - end + opts.merge!( + visibility_level: Gitlab::VisibilityLevel.options['Public'] + ) + end - it 'does not allow a restricted visibility level for non-admins' do - project = create_project(@user, @opts) - expect(project).to respond_to(:errors) - expect(project.errors.messages).to have_key(:visibility_level) - expect(project.errors.messages[:visibility_level].first).to( - match('restricted by your GitLab administrator') - ) - end + it 'does not allow a restricted visibility level for non-admins' do + project = create_project(user, opts) + expect(project).to respond_to(:errors) + expect(project.errors.messages).to have_key(:visibility_level) + expect(project.errors.messages[:visibility_level].first).to( + match('restricted by your GitLab administrator') + ) + end - it 'allows a restricted visibility level for admins' do - admin = create(:admin) - project = create_project(admin, @opts) + it 'allows a restricted visibility level for admins' do + admin = create(:admin) + project = create_project(admin, opts) - expect(project.errors.any?).to be(false) - expect(project.saved?).to be(true) - end + expect(project.errors.any?).to be(false) + expect(project.saved?).to be(true) end + end - context 'repository creation' do - it 'synchronously creates the repository' do - expect_any_instance_of(Project).to receive(:create_repository) + context 'repository creation' do + it 'synchronously creates the repository' do + expect_any_instance_of(Project).to receive(:create_repository) - project = create_project(@user, @opts) - expect(project).to be_valid - expect(project.owner).to eq(@user) - expect(project.namespace).to eq(@user.namespace) - end + project = create_project(user, opts) + expect(project).to be_valid + expect(project.owner).to eq(user) + expect(project.namespace).to eq(user.namespace) end + end - context 'when there is an active service template' do - before do - create(:service, project: nil, template: true, active: true) - end + context 'when there is an active service template' do + before do + create(:service, project: nil, template: true, active: true) + end - it 'creates a service from this template' do - project = create_project(@user, @opts) - project.reload + it 'creates a service from this template' do + project = create_project(user, opts) - expect(project.services.count).to eq 1 - end + expect(project.services.count).to eq 1 end end -- cgit v1.2.1 From afa5afc5e38633126c4c5d318c246239b074155c Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Tue, 22 Nov 2016 16:43:26 +0000 Subject: changes environment.last_deployment to a try expression so it does not fail if environment is not yet set --- app/views/projects/builds/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index d8cbfd7173a..b40d193eb39 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -40,13 +40,13 @@ This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}. - else This build is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}. - - if environment.last_deployment + - if environment.try(:last_deployment) View the most recent deployment #{deployment_link(environment.last_deployment)}. - elsif @build.complete? && !@build.success? The deployment of this build to #{environment_link_for_build(@build.project, @build)} did not succeed. - else This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} - - if environment.last_deployment + - if environment.try(:last_deployment) and will overwrite the = link_to 'latest deployment', deployment_link(environment.last_deployment) -- cgit v1.2.1 From 31a4894a098e5ca0230628677045c2de90961520 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 23 Nov 2016 01:08:54 +0100 Subject: Replace static fixture for shortcuts_issuable_spec (!7685) --- changelogs/unreleased/shortcuts-issuable-fixture.yml | 4 ++++ spec/javascripts/fixtures/issuable.html.haml | 2 -- spec/javascripts/shortcuts_issuable_spec.js | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/shortcuts-issuable-fixture.yml delete mode 100644 spec/javascripts/fixtures/issuable.html.haml diff --git a/changelogs/unreleased/shortcuts-issuable-fixture.yml b/changelogs/unreleased/shortcuts-issuable-fixture.yml new file mode 100644 index 00000000000..88945600886 --- /dev/null +++ b/changelogs/unreleased/shortcuts-issuable-fixture.yml @@ -0,0 +1,4 @@ +--- +title: Replace static fixture for shortcuts_issuable_spec +merge_request: 7685 +author: winniehell diff --git a/spec/javascripts/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml deleted file mode 100644 index 42ab4aa68b1..00000000000 --- a/spec/javascripts/fixtures/issuable.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%form.js-main-target-form - %textarea#note_note diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index 7d36d79b687..e37816b0a8c 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -4,9 +4,11 @@ (function() { describe('ShortcutsIssuable', function() { - fixture.preload('issuable.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('issuable.html'); + fixture.load(fixtureName); + document.querySelector('.js-new-note-form').classList.add('js-main-target-form'); return this.shortcut = new ShortcutsIssuable(); }); return describe('#replyWithSelectedText', function() { -- cgit v1.2.1 From 4b4fd743c35aad51e42d074336191035e7303cf0 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 23 Nov 2016 01:25:11 +0100 Subject: Replace static fixture for zen_mode_spec (!7686) --- changelogs/unreleased/zen-mode-fixture.yml | 4 ++++ spec/javascripts/fixtures/zen_mode.html.haml | 8 -------- spec/javascripts/zen_mode_spec.js | 9 +++++---- 3 files changed, 9 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/zen-mode-fixture.yml delete mode 100644 spec/javascripts/fixtures/zen_mode.html.haml diff --git a/changelogs/unreleased/zen-mode-fixture.yml b/changelogs/unreleased/zen-mode-fixture.yml new file mode 100644 index 00000000000..bec6f6e6dba --- /dev/null +++ b/changelogs/unreleased/zen-mode-fixture.yml @@ -0,0 +1,4 @@ +--- +title: Replace static fixture for zen_mode_spec +merge_request: 7686 +author: winniehell diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml deleted file mode 100644 index cb906a7feaa..00000000000 --- a/spec/javascripts/fixtures/zen_mode.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.md-area - .zen-backdrop - %textarea#note_note.js-gfm-input.markdown-area - %a.js-zen-enter(tabindex="-1" href="#") - %i.fa.fa-expand - Edit in fullscreen - %a.js-zen-leave(tabindex="-1" href="#") - %i.fa.fa-compress diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index a18e8aee9b1..b9acaaa5a0d 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -6,9 +6,10 @@ var enterZen, escapeKeydown, exitZen; describe('ZenMode', function() { - fixture.preload('zen_mode.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('zen_mode.html'); + fixture.load(fixtureName); spyOn(Dropzone, 'forElement').and.callFake(function() { return { enable: function() { @@ -60,11 +61,11 @@ }); enterZen = function() { - return $('a.js-zen-enter').click(); + return $('.js-zen-enter').click(); }; exitZen = function() { // Ohmmmmmmm - return $('a.js-zen-leave').click(); + return $('.js-zen-leave').click(); }; escapeKeydown = function() { -- cgit v1.2.1 From 125cabd335222b61affc74264ee7e88b61324df0 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 22 Nov 2016 15:53:15 +0600 Subject: resolves updated and resolved status is not showin --- app/views/discussions/_discussion.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index e4b4ea675d2..2bce2780484 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -32,6 +32,7 @@ an outdated diff = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago") + = render "discussions/headline", discussion: discussion .discussion-body.js-toggle-content{ class: ("hide" unless expanded) } - if discussion.diff_discussion? && discussion.diff_file -- cgit v1.2.1 From 2a7f4c48c5c98bb007768d8f13e0081be0e133e3 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Wed, 23 Nov 2016 14:31:37 +0600 Subject: resloves failed rspec test --- spec/features/merge_requests/diff_notes_resolve_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index eab64bd4b54..d5e3d8e7eff 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -201,7 +201,7 @@ feature 'Diff notes resolve', feature: true, js: true do expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}") end - expect(page).not_to have_content('Last updated') + expect(page).to have_content('Last updated') page.within '.line-resolve-all-container' do expect(page).to have_content('0/1 discussion resolved') -- cgit v1.2.1 From 78529bbd19252beba7e747f047894033bdc84100 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 Nov 2016 09:36:45 +0100 Subject: Clean build storage after each RSpec test example This prevents subsequent test examples to be affected by previous tests that were executed. --- spec/support/setup_builds_storage.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb index a4f21e95338..a0a7120ceb5 100644 --- a/spec/support/setup_builds_storage.rb +++ b/spec/support/setup_builds_storage.rb @@ -1,6 +1,6 @@ RSpec.configure do |config| def builds_path - Rails.root.join('tmp/builds') + Rails.root.join('tmp/tests/builds') end config.before(:each) do @@ -9,7 +9,7 @@ RSpec.configure do |config| Settings.gitlab_ci['builds_path'] = builds_path end - config.after(:suite) do + config.after(:each) do Dir[File.join(builds_path, '*')].each do |path| next if File.basename(path) == '.gitkeep' -- cgit v1.2.1 From d917ac0f864a25265407947f334c4f8ee2f5a768 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 Nov 2016 09:51:50 +0100 Subject: Clear Carrierwave upload after each test example --- spec/support/carrierwave.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb index aa89afd8fb3..72af2c70324 100644 --- a/spec/support/carrierwave.rb +++ b/spec/support/carrierwave.rb @@ -1,7 +1,7 @@ CarrierWave.root = 'tmp/tests/uploads' RSpec.configure do |config| - config.after(:suite) do + config.after(:each) do FileUtils.rm_rf('tmp/tests/uploads') end end -- cgit v1.2.1 From da553a4f915185f5cc45dfbbe5eba316fe968251 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 23 Nov 2016 08:54:15 +0000 Subject: Fixed timeago when creating new discussion --- app/assets/javascripts/notes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 6cb87f9ba81..4149235ee21 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -332,7 +332,7 @@ gl.diffNotesCompileComponents(); } - gl.utils.localTimeAgo($('.js-timeago', note_html), false); + gl.utils.localTimeAgo($('.js-timeago'), false); return this.updateNotesCount(1); }; -- cgit v1.2.1 From 01f588edbcf1f0f0453cae1677c9372c76e25775 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 Nov 2016 11:52:38 +0100 Subject: Remove entire test files directory for test builds --- spec/support/setup_builds_storage.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb index a0a7120ceb5..fd729434898 100644 --- a/spec/support/setup_builds_storage.rb +++ b/spec/support/setup_builds_storage.rb @@ -3,17 +3,15 @@ RSpec.configure do |config| Rails.root.join('tmp/tests/builds') end + config.before(:suite) do + Settings.gitlab_ci['builds_path'] = builds_path + end + config.before(:each) do FileUtils.mkdir_p(builds_path) - FileUtils.touch(File.join(builds_path, ".gitkeep")) - Settings.gitlab_ci['builds_path'] = builds_path end config.after(:each) do - Dir[File.join(builds_path, '*')].each do |path| - next if File.basename(path) == '.gitkeep' - - FileUtils.rm_rf(path) - end + FileUtils.rm_rf(builds_path) end end -- cgit v1.2.1 From bd3a544ab0417b758cf5e8e5bcd4ae9c0fed1bae Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 23 Nov 2016 19:43:56 +0800 Subject: Wrap against 80 chars and rename failed_or_canceled to better reflect the status. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7508#note_18858398 --- app/models/ci/pipeline.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index c0f2c8ba787..4294a10e9e3 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -169,17 +169,19 @@ module Ci end def cancel_running - Gitlab::OptimisticLocking.retry_lock(statuses.cancelable) do |cancelable| - cancelable.each(&:cancel) - end + Gitlab::OptimisticLocking.retry_lock( + statuses.cancelable) do |cancelable| + cancelable.each(&:cancel) + end end def retry_failed(user) - Gitlab::OptimisticLocking.retry_lock(builds.latest.failed_or_canceled) do |failed| - failed.select(&:retryable?).each do |build| - Ci::Build.retry(build, user) + Gitlab::OptimisticLocking.retry_lock( + builds.latest.failed_or_canceled) do |failed_or_canceled| + failed_or_canceled.select(&:retryable?).each do |build| + Ci::Build.retry(build, user) + end end - end end def mark_as_processable_after_stage(stage_idx) -- cgit v1.2.1 From 71ad3d294ec6ddfb36346e19e6b50ec5eb8d4ef2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 23 Nov 2016 13:27:45 +0100 Subject: Clear test build storage directory before each example --- spec/support/setup_builds_storage.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb index fd729434898..2e7c88bfc09 100644 --- a/spec/support/setup_builds_storage.rb +++ b/spec/support/setup_builds_storage.rb @@ -7,11 +7,12 @@ RSpec.configure do |config| Settings.gitlab_ci['builds_path'] = builds_path end - config.before(:each) do + config.before(:all) do FileUtils.mkdir_p(builds_path) end - config.after(:each) do + config.before(:each) do FileUtils.rm_rf(builds_path) + FileUtils.mkdir_p(builds_path) end end -- cgit v1.2.1 From 5371da341e9d7768ebab8e159b3e2cc8fad1d827 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 23 Nov 2016 14:14:04 +0100 Subject: Remove event caching code Flushing the events cache worked by updating a recent number of rows in the "events" table. This has the result that on PostgreSQL a lot of dead tuples are produced on a regular basis. This in turn means that PostgreSQL will spend considerable amounts of time vacuuming this table. This in turn can lead to an increase of database load. For GitLab.com we measured the impact of not using events caching and found no measurable increase in response timings. Meanwhile not flushing the events cache lead to the "events" table having no more dead tuples as now rows are only inserted into this table. As a result of this we are hereby removing events caching as it does not appear to help and only increases database load. For more information see the following comment: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6578#note_18864037 --- app/controllers/profiles/avatars_controller.rb | 1 - app/controllers/projects/avatars_controller.rb | 1 - app/models/event.rb | 6 ------ app/models/issue.rb | 12 ------------ app/models/merge_request.rb | 12 ------------ app/models/note.rb | 13 ------------- app/models/project.rb | 17 ----------------- app/models/user.rb | 14 -------------- app/services/issuable_base_service.rb | 2 -- app/services/notes/delete_service.rb | 1 - app/services/notes/update_service.rb | 1 - app/services/projects/transfer_service.rb | 3 --- app/uploaders/avatar_uploader.rb | 6 ------ app/views/events/_event.html.haml | 19 +++++++++---------- changelogs/unreleased/events-cache-invalidation.yml | 4 ++++ 15 files changed, 13 insertions(+), 99 deletions(-) create mode 100644 changelogs/unreleased/events-cache-invalidation.yml diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb index f193adb46b4..daa51ae41df 100644 --- a/app/controllers/profiles/avatars_controller.rb +++ b/app/controllers/profiles/avatars_controller.rb @@ -4,7 +4,6 @@ class Profiles::AvatarsController < Profiles::ApplicationController @user.remove_avatar! @user.save - @user.reset_events_cache redirect_to profile_path end diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index ada7db3c552..53788687076 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -20,7 +20,6 @@ class Projects::AvatarsController < Projects::ApplicationController @project.remove_avatar! @project.save - @project.reset_events_cache redirect_to edit_project_path(@project) end diff --git a/app/models/event.rb b/app/models/event.rb index 21eaca917b8..216dba46e74 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -43,12 +43,6 @@ class Event < ActiveRecord::Base scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) } class << self - def reset_event_cache_for(target) - Event.where(target_id: target.id, target_type: target.class.to_s). - order('id DESC').limit(100). - update_all(updated_at: Time.now) - end - # Update Gitlab::ContributionsCalendar#activity_dates if this changes def contributions where("action = ? OR (target_type in (?) AND action in (?))", diff --git a/app/models/issue.rb b/app/models/issue.rb index 6e8f5d3c422..544f830cc69 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -182,18 +182,6 @@ class Issue < ActiveRecord::Base branches_with_iid - branches_with_merge_request end - # Reset issue events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when an issue is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - # To allow polymorphism with MergeRequest. def source_project project diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6c3c093d084..bf9edb0b823 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -601,18 +601,6 @@ class MergeRequest < ActiveRecord::Base self.target_project.repository.branch_names.include?(self.target_branch) end - # Reset merge request events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a merge request is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - def merge_commit_message message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n" message << "#{title}\n\n" diff --git a/app/models/note.rb b/app/models/note.rb index 9ff5e308ed2..9881506edc8 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -198,19 +198,6 @@ class Note < ActiveRecord::Base super(noteable_type.to_s.classify.constantize.base_class.to_s) end - # Reset notes events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a note is updated - # * when a note is removed - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - def editable? !system? end diff --git a/app/models/project.rb b/app/models/project.rb index 76c1fc4945d..21d9ed0f51f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -973,7 +973,6 @@ class Project < ActiveRecord::Base begin gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") send_move_instructions(old_path_with_namespace) - reset_events_cache @old_path_with_namespace = old_path_with_namespace @@ -1040,22 +1039,6 @@ class Project < ActiveRecord::Base attrs end - # Reset events cache related to this project - # - # Since we do cache @event we need to reset cache in special cases: - # * when project was moved - # * when project was renamed - # * when the project avatar changes - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.where(project_id: self.id). - order('id DESC').limit(100). - update_all(updated_at: Time.now) - end - def project_member(user) project_members.find_by(user_id: user) end diff --git a/app/models/user.rb b/app/models/user.rb index 29fb849940a..8a67aa94e79 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -704,20 +704,6 @@ class User < ActiveRecord::Base project.project_member(self) end - # Reset project events cache related to this user - # - # Since we do cache @event we need to reset cache in special cases: - # * when the user changes their avatar - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.where(author_id: id). - order('id DESC').limit(1000). - update_all(updated_at: Time.now) - end - def full_website_url return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\// diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 575795788de..d698b295e6d 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -184,8 +184,6 @@ class IssuableBaseService < BaseService params[:label_ids] = process_label_ids(params, existing_label_ids: issuable.label_ids) if params.present? && update_issuable(issuable, params) - issuable.reset_events_cache - # We do not touch as it will affect a update on updated_at field ActiveRecord::Base.no_touching do handle_common_system_notes(issuable, old_labels: old_labels) diff --git a/app/services/notes/delete_service.rb b/app/services/notes/delete_service.rb index 7f1b30ec84e..a673e8e9dde 100644 --- a/app/services/notes/delete_service.rb +++ b/app/services/notes/delete_service.rb @@ -2,7 +2,6 @@ module Notes class DeleteService < BaseService def execute(note) note.destroy - note.reset_events_cache end end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 1361b1e0300..75a4b3ed826 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -5,7 +5,6 @@ module Notes note.update_attributes(params.merge(updated_by: current_user)) note.create_new_cross_references!(current_user) - note.reset_events_cache if note.previous_changes.include?('note') TodoService.new.update_note(note, current_user) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 28470f59807..34ec575e808 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -61,9 +61,6 @@ module Projects # Move missing group labels to project Labels::TransferService.new(current_user, old_group, project).execute - # clear project cached events - project.reset_events_cache - # Move uploads Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 71ff14a3f20..38683fdf6d7 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -3,16 +3,10 @@ class AvatarUploader < CarrierWave::Uploader::Base storage :file - after :store, :reset_events_cache - def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - def reset_events_cache(file) - model.reset_events_cache if model.is_a?(User) - end - def exists? model.avatar.file && model.avatar.file.exists? end diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 5c318cd3b8b..a0bd14df209 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,14 +3,13 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache [event, current_application_settings, "v2.2"] do - = author_avatar(event, size: 40) + = author_avatar(event, size: 40) - - if event.created_project? - = render "events/event/created_project", event: event - - elsif event.push? - = render "events/event/push", event: event - - elsif event.commented? - = render "events/event/note", event: event - - else - = render "events/event/common", event: event + - if event.created_project? + = render "events/event/created_project", event: event + - elsif event.push? + = render "events/event/push", event: event + - elsif event.commented? + = render "events/event/note", event: event + - else + = render "events/event/common", event: event diff --git a/changelogs/unreleased/events-cache-invalidation.yml b/changelogs/unreleased/events-cache-invalidation.yml new file mode 100644 index 00000000000..2b30f4dcbce --- /dev/null +++ b/changelogs/unreleased/events-cache-invalidation.yml @@ -0,0 +1,4 @@ +--- +title: Remove caching of events data +merge_request: 6578 +author: -- cgit v1.2.1 From 02c6cbf53f4799bc888db7767c2d9a5e04a30002 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 23 Nov 2016 14:18:06 +0100 Subject: Add tests --- app/views/projects/builds/show.html.haml | 3 +- spec/views/projects/builds/show.html.haml_spec.rb | 52 ++++++++++++++++++----- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index b40d193eb39..108674dbba6 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -40,8 +40,7 @@ This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}. - else This build is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}. - - if environment.try(:last_deployment) - View the most recent deployment #{deployment_link(environment.last_deployment)}. + View the most recent deployment #{deployment_link(environment.last_deployment)}. - elsif @build.complete? && !@build.success? The deployment of this build to #{environment_link_for_build(@build.project, @build)} did not succeed. - else diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index e0c77201116..869dcb417e6 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -88,16 +88,46 @@ describe 'projects/builds/show', :view do create(:ci_build, :running, environment: 'staging', pipeline: pipeline) end - let!(:environment) do - create(:environment, name: 'staging', project: project) - end - - it 'shows deployment message' do - expected_text = 'This build is creating a deployment to staging' - render - - expect(rendered).to have_css( - '.environment-information', text: expected_text) + context 'and environment does exist' do + let!(:environment) do + create(:environment, name: 'staging', project: project) + end + + it 'shows deployment message' do + expected_text = 'This build is creating a deployment to staging' + render + + expect(rendered).to have_css( + '.environment-information', text: expected_text) + end + + context 'and has deployment' do + let!(:deployment) do + create(:deployment, environment: environment) + end + + it 'shows that deployment will be overwritten' do + expected_text = 'This build is creating a deployment to staging' + render + + expect(rendered).to have_css( + '.environment-information', text: expected_text) + expect(rendered).to have_css( + '.environment-information', text: 'latest deployment') + end + end + end + + context 'and environment does not exist' do + it 'shows deployment message' do + expected_text = 'This build is creating a deployment to staging' + render + + expect(rendered).to have_css( + '.environment-information', text: expected_text) + expect(rendered).not_to have_css( + '.environment-information', text: 'latest deployment') + end end end @@ -134,6 +164,8 @@ describe 'projects/builds/show', :view do expect(rendered).to have_css( '.environment-information', text: expected_text) + expect(rendered).not_to have_css( + '.environment-information', text: 'latest deployment') end end end -- cgit v1.2.1 From c18f96cfe950b11e2784479cbc7e518667273143 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 23 Nov 2016 20:13:24 +0500 Subject: Move admin spam spinach test to Rspec https://gitlab.com/gitlab-org/gitlab-ce/issues/23036 --- .../move-admin-spam-spinach-test-to-rspec.yml | 4 ++++ features/admin/spam_logs.feature | 8 ------- features/steps/admin/spam_logs.rb | 28 ---------------------- spec/features/admin/admin_browse_spam_logs_spec.rb | 22 +++++++++++++++++ 4 files changed, 26 insertions(+), 36 deletions(-) create mode 100644 changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml delete mode 100644 features/admin/spam_logs.feature delete mode 100644 features/steps/admin/spam_logs.rb create mode 100644 spec/features/admin/admin_browse_spam_logs_spec.rb diff --git a/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml b/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml new file mode 100644 index 00000000000..a7ec2c20554 --- /dev/null +++ b/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml @@ -0,0 +1,4 @@ +--- +title: Move admin spam spinach test to Rspec +merge_request: 7708 +author: Semyon Pupkov diff --git a/features/admin/spam_logs.feature b/features/admin/spam_logs.feature deleted file mode 100644 index 92a5389e3a4..00000000000 --- a/features/admin/spam_logs.feature +++ /dev/null @@ -1,8 +0,0 @@ -Feature: Admin spam logs - Background: - Given I sign in as an admin - And spam logs exist - - Scenario: Browse spam logs - When I visit spam logs page - Then I should see list of spam logs diff --git a/features/steps/admin/spam_logs.rb b/features/steps/admin/spam_logs.rb deleted file mode 100644 index ad825fd414c..00000000000 --- a/features/steps/admin/spam_logs.rb +++ /dev/null @@ -1,28 +0,0 @@ -class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedAdmin - - step 'I should see list of spam logs' do - expect(page).to have_content('Spam Logs') - expect(page).to have_content spam_log.source_ip - expect(page).to have_content spam_log.noteable_type - expect(page).to have_content 'N' - expect(page).to have_content spam_log.title - expect(page).to have_content truncate(spam_log.description) - expect(page).to have_link('Remove user') - expect(page).to have_link('Block user') - end - - step 'spam logs exist' do - create(:spam_log) - end - - def spam_log - @spam_log ||= SpamLog.first - end - - def truncate(description) - "#{spam_log.description[0...97]}..." - end -end diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb new file mode 100644 index 00000000000..562ace92598 --- /dev/null +++ b/spec/features/admin/admin_browse_spam_logs_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'Admin browse spam logs' do + let!(:spam_log) { create(:spam_log) } + + before do + login_as :admin + end + + scenario 'Browse spam logs' do + visit admin_spam_logs_path + + expect(page).to have_content('Spam Logs') + expect(page).to have_content(spam_log.source_ip) + expect(page).to have_content(spam_log.noteable_type) + expect(page).to have_content('N') + expect(page).to have_content(spam_log.title) + expect(page).to have_content("#{spam_log.description[0...97]}...") + expect(page).to have_link('Remove user') + expect(page).to have_link('Block user') + end +end -- cgit v1.2.1 From 42fe0cac09fc335a8704b23bfbd219645914ee41 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 21 Nov 2016 09:09:19 +0100 Subject: Upgrade grape-entity to 0.6.0 --- Gemfile | 2 +- Gemfile.lock | 5 +++-- changelogs/unreleased/zj-upgrade-grape.yml | 2 +- spec/serializers/environment_serializer_spec.rb | 4 ---- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 14ea7ec7ea0..cd463fbd82f 100644 --- a/Gemfile +++ b/Gemfile @@ -68,7 +68,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist' # API gem 'grape', '~> 0.15.0' -gem 'grape-entity', '~> 0.5.2' +gem 'grape-entity', '~> 0.6.0' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' # Pagination diff --git a/Gemfile.lock b/Gemfile.lock index 331ffd89148..cf2ebaa00d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -316,7 +316,8 @@ GEM rack-accept rack-mount virtus (>= 1.0.0) - grape-entity (0.5.2) + grape-entity (0.6.0) + activesupport multi_json (>= 1.3.2) haml (4.0.7) tilt @@ -868,7 +869,7 @@ DEPENDENCIES gollum-rugged_adapter (~> 0.4.2) gon (~> 6.1.0) grape (~> 0.15.0) - grape-entity (~> 0.5.2) + grape-entity (~> 0.6.0) haml_lint (~> 0.18.2) hamlit (~> 2.6.1) health_check (~> 2.2.0) diff --git a/changelogs/unreleased/zj-upgrade-grape.yml b/changelogs/unreleased/zj-upgrade-grape.yml index 75a72283d1e..1df42d98733 100644 --- a/changelogs/unreleased/zj-upgrade-grape.yml +++ b/changelogs/unreleased/zj-upgrade-grape.yml @@ -1,4 +1,4 @@ --- -title: Update grape entity to 0.5.2 +title: Update grape entity to 0.6.0 merge_request: 7491 author: diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index 8f95c9250b0..b7ed4eb0239 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -27,10 +27,6 @@ describe EnvironmentSerializer do let(:deployable) { create(:ci_build) } let(:resource) { deployment.environment } - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of environment' do expect(json) .to include(:name, :external_url, :environment_path, :last_deployment) -- cgit v1.2.1 From 4b3c1e56ae7468d0234240dd211d54a7abd39f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 21 Nov 2016 16:31:51 +0100 Subject: Move LfsHelper to a new LfsRequest concern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also create a new WorkhorseRequest concern Signed-off-by: Rémy Coutable --- app/controllers/concerns/lfs_request.rb | 109 +++++++++++++++++++++ app/controllers/concerns/workhorse_request.rb | 13 +++ .../projects/git_http_client_controller.rb | 16 +-- app/controllers/projects/git_http_controller.rb | 12 ++- app/controllers/projects/lfs_api_controller.rb | 21 ++-- app/controllers/projects/lfs_storage_controller.rb | 7 +- app/helpers/lfs_helper.rb | 85 ---------------- 7 files changed, 150 insertions(+), 113 deletions(-) create mode 100644 app/controllers/concerns/lfs_request.rb create mode 100644 app/controllers/concerns/workhorse_request.rb delete mode 100644 app/helpers/lfs_helper.rb diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb new file mode 100644 index 00000000000..ed22b1e5470 --- /dev/null +++ b/app/controllers/concerns/lfs_request.rb @@ -0,0 +1,109 @@ +# This concern assumes: +# - a `#project` accessor +# - a `#user` accessor +# - a `#authentication_result` accessor +# - a `#can?(object, action, subject)` method +# - a `#ci?` method +# - a `#download_request?` method +# - a `#upload_request?` method +# - a `#has_authentication_ability?(ability)` method +module LfsRequest + extend ActiveSupport::Concern + + included do + before_action :require_lfs_enabled! + before_action :lfs_check_access! + end + + private + + def require_lfs_enabled! + return if Gitlab.config.lfs.enabled + + render( + json: { + message: 'Git LFS is not enabled on this GitLab server, contact your admin.', + documentation_url: help_url, + }, + status: 501 + ) + end + + def lfs_check_access! + return if download_request? && lfs_download_access? + return if upload_request? && lfs_upload_access? + + if project.public? || can?(user, :read_project, project) + lfs_forbidden! + else + render_lfs_not_found + end + end + + def lfs_forbidden! + render_lfs_forbidden + end + + def render_lfs_forbidden + render( + json: { + message: 'Access forbidden. Check your access level.', + documentation_url: help_url, + }, + content_type: "application/vnd.git-lfs+json", + status: 403 + ) + end + + def render_lfs_not_found + render( + json: { + message: 'Not found.', + documentation_url: help_url, + }, + content_type: "application/vnd.git-lfs+json", + status: 404 + ) + end + + def lfs_download_access? + return false unless project.lfs_enabled? + + ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? + end + + def lfs_upload_access? + return false unless project.lfs_enabled? + + has_authentication_ability?(:push_code) && can?(user, :push_code, project) + end + + def lfs_deploy_token? + authentication_result.lfs_deploy_token?(project) + end + + def user_can_download_code? + has_authentication_ability?(:download_code) && can?(user, :download_code, project) + end + + def build_can_download_code? + has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) + end + + def storage_project + @storage_project ||= begin + result = project + + loop do + break unless result.forked? + result = result.forked_from_project + end + + result + end + end + + def objects + @objects ||= (params[:objects] || []).to_a + end +end diff --git a/app/controllers/concerns/workhorse_request.rb b/app/controllers/concerns/workhorse_request.rb new file mode 100644 index 00000000000..43c0f1b173c --- /dev/null +++ b/app/controllers/concerns/workhorse_request.rb @@ -0,0 +1,13 @@ +module WorkhorseRequest + extend ActiveSupport::Concern + + included do + before_action :verify_workhorse_api! + end + + private + + def verify_workhorse_api! + Gitlab::Workhorse.verify_api_request!(request.headers) + end +end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 3f41916e6d3..8714349e27f 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -18,6 +18,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController private + def download_request? + raise NotImplementedError + end + + def upload_request? + raise NotImplementedError + end + def authenticate_user @authentication_result = Gitlab::Auth::Result.new @@ -130,10 +138,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController authentication_result.ci?(project) end - def lfs_deploy_token? - authentication_result.lfs_deploy_token?(project) - end - def authentication_has_download_access? has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) end @@ -149,8 +153,4 @@ class Projects::GitHttpClientController < Projects::ApplicationController def authentication_project authentication_result.project end - - def verify_workhorse_api! - Gitlab::Workhorse.verify_api_request!(request.headers) - end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 13caeb42d40..9184dcccac5 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,7 +1,5 @@ -# This file should be identical in GitLab Community Edition and Enterprise Edition - class Projects::GitHttpController < Projects::GitHttpClientController - before_action :verify_workhorse_api! + include WorkhorseRequest # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) @@ -67,14 +65,18 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def render_denied - if user && user.can?(:read_project, project) - render plain: 'Access denied', status: :forbidden + if user && can?(user, :read_project, project) + render plain: access_denied_message, status: :forbidden else # Do not leak information about project existence render_not_found end end + def access_denied_message + 'Access denied' + end + def upload_pack_allowed? return false unless Gitlab.config.gitlab_shell.upload_pack diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index 2d493276941..440259b643c 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -1,8 +1,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController - include LfsHelper + include LfsRequest - before_action :require_lfs_enabled! - before_action :lfs_check_access!, except: [:deprecated] + skip_before_action :lfs_check_access!, only: [:deprecated] def batch unless objects.present? @@ -31,6 +30,14 @@ class Projects::LfsApiController < Projects::GitHttpClientController private + def download_request? + params[:operation] == 'download' + end + + def upload_request? + params[:operation] == 'upload' + end + def existing_oids @existing_oids ||= begin storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) @@ -79,12 +86,4 @@ class Projects::LfsApiController < Projects::GitHttpClientController } } end - - def download_request? - params[:operation] == 'download' - end - - def upload_request? - params[:operation] == 'upload' - end end diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb index 9005b104e90..32759672b6c 100644 --- a/app/controllers/projects/lfs_storage_controller.rb +++ b/app/controllers/projects/lfs_storage_controller.rb @@ -1,9 +1,8 @@ class Projects::LfsStorageController < Projects::GitHttpClientController - include LfsHelper + include LfsRequest + include WorkhorseRequest - before_action :require_lfs_enabled! - before_action :lfs_check_access! - before_action :verify_workhorse_api!, only: [:upload_authorize] + skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize] def download lfs_object = LfsObject.find_by_oid(oid) diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb deleted file mode 100644 index 2425c3a8bc8..00000000000 --- a/app/helpers/lfs_helper.rb +++ /dev/null @@ -1,85 +0,0 @@ -module LfsHelper - include Gitlab::Routing.url_helpers - - def require_lfs_enabled! - return if Gitlab.config.lfs.enabled - - render( - json: { - message: 'Git LFS is not enabled on this GitLab server, contact your admin.', - documentation_url: help_url, - }, - status: 501 - ) - end - - def lfs_check_access! - return if download_request? && lfs_download_access? - return if upload_request? && lfs_upload_access? - - if project.public? || (user && user.can?(:read_project, project)) - render_lfs_forbidden - else - render_lfs_not_found - end - end - - def lfs_download_access? - return false unless project.lfs_enabled? - - ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? - end - - def objects - @objects ||= (params[:objects] || []).to_a - end - - def user_can_download_code? - has_authentication_ability?(:download_code) && can?(user, :download_code, project) - end - - def build_can_download_code? - has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) - end - - def lfs_upload_access? - return false unless project.lfs_enabled? - - has_authentication_ability?(:push_code) && can?(user, :push_code, project) - end - - def render_lfs_forbidden - render( - json: { - message: 'Access forbidden. Check your access level.', - documentation_url: help_url, - }, - content_type: "application/vnd.git-lfs+json", - status: 403 - ) - end - - def render_lfs_not_found - render( - json: { - message: 'Not found.', - documentation_url: help_url, - }, - content_type: "application/vnd.git-lfs+json", - status: 404 - ) - end - - def storage_project - @storage_project ||= begin - result = project - - loop do - break unless result.forked? - result = result.forked_from_project - end - - result - end - end -end -- cgit v1.2.1 From 0a1f519c2af4bd5f67e1a74701d98583047da117 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 22 Nov 2016 20:04:57 -0500 Subject: Take only objects for the events list --- .../javascripts/cycle_analytics/cycle_analytics_store.js.es6 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 index 9b905874167..be732971c7f 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 @@ -62,9 +62,11 @@ this.state.events = this.decorateEvents(events); }, decorateEvents(events) { - const newEvents = events; + const newEvents = []; + + events.forEach((item) => { + if (!item) return; - newEvents.forEach((item) => { item.totalTime = item.total_time; item.author.webUrl = item.author.web_url; item.author.avatarUrl = item.author.avatar_url; @@ -79,6 +81,8 @@ delete item.created_at; delete item.short_sha; delete item.commit_url; + + newEvents.push(item); }); return newEvents; -- cgit v1.2.1 From 0dc108507479b7dbd1d31ceab1989e13f766d613 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 23 Nov 2016 19:53:12 +0100 Subject: Remove tests which do not add value --- spec/serializers/analytics_build_serializer_spec.rb | 4 ---- spec/serializers/analytics_issue_serializer_spec.rb | 4 ---- spec/serializers/analytics_merge_request_serializer_spec.rb | 4 ---- 3 files changed, 12 deletions(-) diff --git a/spec/serializers/analytics_build_serializer_spec.rb b/spec/serializers/analytics_build_serializer_spec.rb index a0a9d9a5f12..f0551c78671 100644 --- a/spec/serializers/analytics_build_serializer_spec.rb +++ b/spec/serializers/analytics_build_serializer_spec.rb @@ -10,10 +10,6 @@ describe AnalyticsBuildSerializer do let(:resource) { create(:ci_build) } context 'when there is a single object provided' do - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of analyticsBuild' do expect(json) .to include(:name, :branch, :short_sha, :date, :total_time, :url, :author) diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb index 2842e1ba52f..6afbb2df35c 100644 --- a/spec/serializers/analytics_issue_serializer_spec.rb +++ b/spec/serializers/analytics_issue_serializer_spec.rb @@ -22,10 +22,6 @@ describe AnalyticsIssueSerializer do end context 'when there is a single object provided' do - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of the issue' do expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author) end diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb index 564207984df..cdfae27193f 100644 --- a/spec/serializers/analytics_merge_request_serializer_spec.rb +++ b/spec/serializers/analytics_merge_request_serializer_spec.rb @@ -23,10 +23,6 @@ describe AnalyticsMergeRequestSerializer do end context 'when there is a single object provided' do - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of the merge request' do expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author, :state) end -- cgit v1.2.1 From a7486bcb77fff619e4e6b0ec05423b08db3d0fe9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 23 Nov 2016 14:34:49 +0000 Subject: Fixed commit time not rendering after initial page load Closes #24862 --- app/assets/javascripts/commits.js | 6 ++++-- changelogs/unreleased/fixed-commit-timeago.yml | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/fixed-commit-timeago.yml diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 951fb338f9d..3627aaf5080 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len, prefer-arrow-callback */ (function() { this.CommitsList = (function() { function CommitsList() {} @@ -13,7 +13,9 @@ return false; } }); - Pager.init(limit, false); + Pager.init(limit, false, false, function() { + gl.utils.localTimeAgo($('.js-timeago')); + }); this.content = $("#commits-list"); this.searchField = $("#commits-search"); return this.initSearch(); diff --git a/changelogs/unreleased/fixed-commit-timeago.yml b/changelogs/unreleased/fixed-commit-timeago.yml new file mode 100644 index 00000000000..295d8db63d0 --- /dev/null +++ b/changelogs/unreleased/fixed-commit-timeago.yml @@ -0,0 +1,4 @@ +--- +title: Fixed commit timeago not rendering after initial page +merge_request: +author: -- cgit v1.2.1 From 0fcb9cf854895d3515fd4dc295e8d6a44f9c2aa3 Mon Sep 17 00:00:00 2001 From: PotHix Date: Wed, 23 Nov 2016 17:33:12 -0200 Subject: Add missing documentation. Build was being triggered but there was no documentation about it, so I'm adding what I was receiving from webhooks when implementing the Rocketchat integration (https://github.com/PotHix/rocketchat-gitlab-hook). --- doc/web_hooks/web_hooks.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 33c1a79d59c..cd37189fdd2 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -922,6 +922,64 @@ X-Gitlab-Event: Pipeline Hook } ``` + +## Build events + +Triggered on status change of a Build. + +**Request Header**: + +``` +X-Gitlab-Event: Build Hook +``` + +**Request Body**: + +``` +{ + "object_kind": "build", + "ref": "gitlab-script-trigger", + "tag": false, + "before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", + "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", + "build_id": 1977, + "build_name": "test", + "build_stage": "test", + "build_status": "created", + "build_started_at": null, + "build_finished_at": null, + "build_duration": null, + "build_allow_failure": false, + "project_id": 380, + "project_name": "gitlab-org/gitlab-test", + "user": { + "id": 3, + "name": "User", + "email": "user@gitlab.com" + }, + "commit": { + "id": 2366, + "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", + "message": "test\n", + "author_name": "User", + "author_email": "user@gitlab.com", + "status": "created", + "duration": null, + "started_at": null, + "finished_at": null + }, + "repository": { + "name": "gitlab_test", + "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", + "description": "Atque in sunt eos similique dolores voluptatem.", + "homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test", + "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", + "git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git", + "visibility_level": 20 + } +} +``` + #### Example webhook receiver If you want to see GitLab's webhooks in action for testing purposes you can use -- cgit v1.2.1 From 6a23fb305f830735b601d0362d4cf881ddc08629 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 23 Nov 2016 16:48:51 -0600 Subject: allow "." in group name validation regex --- app/views/shared/_group_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index ba25e09d638..fc1753ca082 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -13,7 +13,7 @@ .input-group-addon = root_url = f.text_field :path, placeholder: 'open-source', class: 'form-control', - autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+", + autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_\\.]+", required: true, title: 'Please choose a group name with no special characters.' - if @group.persisted? -- cgit v1.2.1 From 5b9c4f48724c8a8eb9556719239be996366fd005 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 23 Nov 2016 17:31:35 -0600 Subject: properly escape username validation error message flash --- app/views/profiles/update_username.js.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml index de1337a2a24..5307e0b48cb 100644 --- a/app/views/profiles/update_username.js.haml +++ b/app/views/profiles/update_username.js.haml @@ -2,5 +2,6 @@ :plain new Flash("Username successfully changed", "notice") - else + - error = @user.errors.full_messages.first :plain - new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") + new Flash("Username change failed - #{escape_javascript error.html_safe}", "alert") -- cgit v1.2.1 From 5e26745e312a5c792f5864d46fb0378d90790504 Mon Sep 17 00:00:00 2001 From: Luis Alonso Chavez Armendariz Date: Wed, 23 Nov 2016 16:34:58 -0700 Subject: Fix title case to sentence case --- app/views/shared/issuable/_sidebar.html.haml | 6 +++--- changelogs/unreleased/issue_24748.yml | 4 ++++ doc/workflow/img/todos_add_todo_sidebar.png | Bin 33350 -> 42360 bytes doc/workflow/img/todos_mark_done_sidebar.png | Bin 33703 -> 42317 bytes doc/workflow/todos.md | 4 ++-- spec/features/issues/todo_spec.rb | 8 ++++---- spec/javascripts/fixtures/right_sidebar.html.haml | 4 ++-- 7 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/issue_24748.yml diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f166fac105d..02427650219 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -9,12 +9,12 @@ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } = sidebar_gutter_toggle_icon - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } %span.js-issuable-todo-text - if todo - Mark Done + Mark done - else - Add Todo + Add todo = icon('spin spinner', class: 'hidden js-issuable-todo-loading') = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| diff --git a/changelogs/unreleased/issue_24748.yml b/changelogs/unreleased/issue_24748.yml new file mode 100644 index 00000000000..4c1df542c53 --- /dev/null +++ b/changelogs/unreleased/issue_24748.yml @@ -0,0 +1,4 @@ +--- +title: Fix title case to sentence case +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/doc/workflow/img/todos_add_todo_sidebar.png b/doc/workflow/img/todos_add_todo_sidebar.png index 59175ae44c5..3fa37067d1e 100644 Binary files a/doc/workflow/img/todos_add_todo_sidebar.png and b/doc/workflow/img/todos_add_todo_sidebar.png differ diff --git a/doc/workflow/img/todos_mark_done_sidebar.png b/doc/workflow/img/todos_mark_done_sidebar.png index aa35bb672ea..a8e756a71db 100644 Binary files a/doc/workflow/img/todos_mark_done_sidebar.png and b/doc/workflow/img/todos_mark_done_sidebar.png differ diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index 54e7ae19ea5..1a8fc39bb33 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -35,7 +35,7 @@ A Todo appears in your Todos dashboard when: ### Manually creating a Todo You can also add an issue or merge request to your Todos dashboard by clicking -the "Add Todo" button in the issue or merge request sidebar. +the "Add todo" button in the issue or merge request sidebar. ![Adding a Todo from the issuable sidebar](img/todos_add_todo_sidebar.png) @@ -69,7 +69,7 @@ corresponding **Done** button, and it will disappear from your Todo list. ![A Todo in the Todos dashboard](img/todo_list_item.png) A Todo can also be marked as done from the issue or merge request sidebar using -the "Mark Done" button. +the "Mark done" button. ![Mark Done from the issuable sidebar](img/todos_mark_done_sidebar.png) diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index de8fdda388d..41ff31d2b99 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -13,8 +13,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'creates todo when clicking button' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - expect(page).to have_content 'Mark Done' + click_button 'Add todo' + expect(page).to have_content 'Mark done' end page.within '.header-content .todos-pending-count' do @@ -30,8 +30,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'marks a todo as done' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - click_button 'Mark Done' + click_button 'Add todo' + click_button 'Mark done' end expect(page).to have_selector('.todos-pending-count', visible: false) diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml index d48b77cf0ce..d259b58f235 100644 --- a/spec/javascripts/fixtures/right_sidebar.html.haml +++ b/spec/javascripts/fixtures/right_sidebar.html.haml @@ -5,9 +5,9 @@ %div.block.issuable-sidebar-header %a.gutter-toggle.pull-right.js-sidebar-toggle %i.fa.fa-angle-double-left - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} %span.js-issuable-todo-text - Add Todo + Add todo %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden %form.issuable-context-form -- cgit v1.2.1 From b437d305ca92f0f908d2cba4d681a8c8df9a348a Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Wed, 23 Nov 2016 19:27:22 -0500 Subject: Add default_branch attr to Project API payload in docs. --- doc/api/projects.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/api/projects.md b/doc/api/projects.md index de5d3b07c21..b02a901d884 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -592,6 +592,7 @@ Parameters: | `name` | string | yes | The name of the new project | | `path` | string | no | Custom repository name for new project. By default generated based on name | | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | +| `default_branch` | string | no | `master` by default | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | | `merge_requests_enabled` | boolean | no | Enable merge requests for this project | @@ -657,6 +658,7 @@ Parameters: | `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | | `name` | string | yes | The name of the project | | `path` | string | no | Custom repository name for the project. By default generated based on name | +| `default_branch` | string | no | `master` by default | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | | `merge_requests_enabled` | boolean | no | Enable merge requests for this project | -- cgit v1.2.1 From ba5e98bb701672d0cf1d98a80272c16a754ec83c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 24 Nov 2016 14:32:32 +0800 Subject: Backport Note#commands_changes from EE --- app/controllers/projects/notes_controller.rb | 1 + app/models/note.rb | 3 +++ app/services/notes/create_service.rb | 2 ++ 3 files changed, 6 insertions(+) diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index f029fde2a2f..15ca080c696 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -197,6 +197,7 @@ class Projects::NotesController < Projects::ApplicationController ) end + attrs[:commands_changes] = note.commands_changes unless attrs[:award] attrs end diff --git a/app/models/note.rb b/app/models/note.rb index 9ff5e308ed2..ed4224e3046 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -19,6 +19,9 @@ class Note < ActiveRecord::Base # Banzai::ObjectRenderer attr_accessor :user_visible_reference_count + # Attribute used to store the attributes that have ben changed by slash commands. + attr_accessor :commands_changes + default_value_for :system, false attr_mentionable :note, pipeline: :note diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 7935fabe2da..d75592e31f3 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -43,6 +43,8 @@ module Notes if only_commands note.errors.add(:commands_only, 'Your commands have been executed!') end + + note.commands_changes = command_params.keys end note -- cgit v1.2.1 From a43f71ec144c1a8ab9f9829414699cec062a8b92 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 24 Nov 2016 00:21:48 -0800 Subject: Hide project variables values by default Add a button to reveal/hide the values to help prevent accidental disclosure of sensitive information from wandering on a page. Closes #21358 --- app/assets/javascripts/dispatcher.js.es6 | 3 ++ app/assets/javascripts/project_variables.js.es6 | 44 +++++++++++++++++++++++++ app/assets/stylesheets/pages/projects.scss | 8 +++++ app/views/projects/variables/_table.html.haml | 4 +-- app/views/projects/variables/index.html.haml | 1 + 5 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/project_variables.js.es6 diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index c2d4670b7e9..16df4b0b005 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -208,6 +208,9 @@ new gl.ProtectedBranchCreate(); new gl.ProtectedBranchEditList(); break; + case 'projects:variables:index': + new gl.ProjectVariables(); + break; } switch (path.first()) { case 'admin': diff --git a/app/assets/javascripts/project_variables.js.es6 b/app/assets/javascripts/project_variables.js.es6 new file mode 100644 index 00000000000..51ee55946e4 --- /dev/null +++ b/app/assets/javascripts/project_variables.js.es6 @@ -0,0 +1,44 @@ +/* eslint-disable */ +((global) => { + const HIDDEN_VALUE_TEXT = '******'; + + class ProjectVariables { + constructor() { + this.$reveal = $('.js-btn-toggle-reveal-values'); + + this.$reveal.on('click', this.toggleRevealState.bind(this)); + } + + toggleRevealState(event) { + event.preventDefault(); + + const $btn = $(event.currentTarget); + const oldStatus = $btn.attr('data-status'); + + if (oldStatus == 'hidden') { + [newStatus, newAction] = ['revealed', 'Hide Values']; + } else { + [newStatus, newAction] = ['hidden', 'Reveal Values']; + } + + $btn.attr('data-status', newStatus); + + $variables = $('.variable-value'); + + for (let variable of $variables) { + let $variable = $(variable); + let newText = HIDDEN_VALUE_TEXT; + + if (newStatus == 'revealed') { + newText = $variable.attr('data-value'); + } + + $variable.text(newText); + } + + $btn.text(newAction); + } + } + + global.ProjectVariables = ProjectVariables; +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 19a7a97ea0d..0562ee7b178 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -876,3 +876,11 @@ pre.light-well { pointer-events: none; } } + +.variables-table { + table-layout: fixed; + + .variable-key { + width: 30%; + } +} diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml index 07cee86ba4c..c7cebf45160 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/projects/variables/_table.html.haml @@ -12,8 +12,8 @@ - @project.variables.order_key_asc.each do |variable| - if variable.id? %tr - %td= variable.key - %td= variable.value + %td.variable-key= variable.key + %td.variable-value{ "data-value" => variable.value }****** %td = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do %span.sr-only diff --git a/app/views/projects/variables/index.html.haml b/app/views/projects/variables/index.html.haml index 09bb54600af..39303700131 100644 --- a/app/views/projects/variables/index.html.haml +++ b/app/views/projects/variables/index.html.haml @@ -15,3 +15,4 @@ No variables found, add one with the form above. - else = render "table" + %button.btn.btn-info.js-btn-toggle-reveal-values{"data-status" => 'hidden'} Reveal Values -- cgit v1.2.1 From 4d2e7894efa36cc1b5de9432e25fcf22b6cf1d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 23 Nov 2016 19:18:19 +0100 Subject: Make API::Helpers find a project with only one query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../22373-reduce-queries-in-api-helpers-find_project.yml | 4 ++++ lib/api/helpers.rb | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml diff --git a/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml b/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml new file mode 100644 index 00000000000..7f1d40e7c21 --- /dev/null +++ b/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml @@ -0,0 +1,4 @@ +--- +title: 'Make API::Helpers find a project with only one query' +merge_request: 7714 +author: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2c593dbb4ea..60067758e95 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -76,7 +76,12 @@ module API end def find_project(id) - project = Project.find_with_namespace(id) || Project.find_by(id: id) + project = + if id =~ /^\d+$/ + Project.find_by(id: id) + else + Project.find_with_namespace(id) + end if can?(current_user, :read_project, project) project -- cgit v1.2.1 From 39378d0e976d9552e96b300cb7f43874424d13b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 23 Nov 2016 12:58:16 +0100 Subject: Document that we always use `do...end` for `before` in RSpec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- doc/development/testing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/testing.md b/doc/development/testing.md index 6106e47daa0..dbea6b9c9aa 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -63,6 +63,8 @@ the command line via `bundle exec teaspoon`, or via a web browser at - Use `.method` to describe class methods and `#method` to describe instance methods. - Use `context` to test branching logic. +- Use multi-line `do...end` blocks for `before` and `after`, even when it would + fit on a single line. - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). - Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). - Don't supply the `:each` argument to hooks since it's the default. -- cgit v1.2.1 From 391db2421eda4f69e73f18d45fa377f209670528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 10:17:24 +0100 Subject: Fix documentation to create the `pg_trm` extension before creating the DB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- doc/install/installation.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index b5e2640b380..dabd69d7466 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -175,17 +175,17 @@ We recommend using a PostgreSQL database. For MySQL check the ```bash sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" ``` - -1. Create the GitLab production database and grant all privileges on database: + +1. Create the `pg_trgm` extension (required for GitLab 8.6+): ```bash - sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;" + sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" ``` -1. Create the `pg_trgm` extension (required for GitLab 8.6+): +1. Create the GitLab production database and grant all privileges on database: ```bash - sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;" ``` 1. Try connecting to the new database with the new user: -- cgit v1.2.1 From 3341a9d80c884113b6208397717657dc99266683 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 24 Nov 2016 11:01:56 +0100 Subject: Add missing JIRA file that redirects to the new location [ci skip] --- doc/integration/jira.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/integration/jira.md diff --git a/doc/integration/jira.md b/doc/integration/jira.md new file mode 100644 index 00000000000..e2f136bcc35 --- /dev/null +++ b/doc/integration/jira.md @@ -0,0 +1,3 @@ +# GitLab JIRA integration + +This document was moved to [project_services/jira](../project_services/jira.md). -- cgit v1.2.1 From 1fa55069745163e70f01349f71798e2a214ae28e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 24 Nov 2016 02:24:52 -0800 Subject: Add spec for hiding variables and remove the need for ES6 Symbol --- app/assets/javascripts/project_variables.js.es6 | 6 +++--- spec/features/variables_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/project_variables.js.es6 b/app/assets/javascripts/project_variables.js.es6 index 51ee55946e4..6c905f58c85 100644 --- a/app/assets/javascripts/project_variables.js.es6 +++ b/app/assets/javascripts/project_variables.js.es6 @@ -23,9 +23,9 @@ $btn.attr('data-status', newStatus); - $variables = $('.variable-value'); + let $variables = $('.variable-value'); - for (let variable of $variables) { + $variables.each(function (_, variable) { let $variable = $(variable); let newText = HIDDEN_VALUE_TEXT; @@ -34,7 +34,7 @@ } $variable.text(newText); - } + }); $btn.text(newAction); } diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb index d7880d5778f..ff30ffd7820 100644 --- a/spec/features/variables_spec.rb +++ b/spec/features/variables_spec.rb @@ -29,6 +29,31 @@ describe 'Project variables', js: true do end end + it 'reveals and hides new variable' do + fill_in('variable_key', with: 'key') + fill_in('variable_value', with: 'key value') + click_button('Add new variable') + + page.within('.variables-table') do + expect(page).to have_content('key') + expect(page).to have_content('******') + end + + click_button('Reveal Values') + + page.within('.variables-table') do + expect(page).to have_content('key') + expect(page).to have_content('key value') + end + + click_button('Hide Values') + + page.within('.variables-table') do + expect(page).to have_content('key') + expect(page).to have_content('******') + end + end + it 'deletes variable' do page.within('.variables-table') do find('.btn-variable-delete').click -- cgit v1.2.1 From 6df22f72c6c312199c547e017ce1f947cf88e34c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 23 Nov 2016 14:55:23 +0800 Subject: Rephrase some system notes to be compatible with new system note style --- app/services/notification_service.rb | 1 - app/services/system_note_service.rb | 86 ++++++++++----------- changelogs/unreleased/rephrase-system-notes.yml | 4 + doc/api/notes.md | 2 +- features/steps/project/merge_requests.rb | 2 +- features/steps/shared/issuable.rb | 2 +- .../projects/milestones_controller_spec.rb | 2 +- spec/features/issues/move_spec.rb | 6 +- spec/features/issues/new_branch_button_spec.rb | 4 +- .../merge_when_build_succeeds_spec.rb | 2 +- spec/features/notes_on_merge_requests_spec.rb | 2 +- spec/models/note_spec.rb | 2 +- spec/requests/api/notes_spec.rb | 2 +- spec/services/issues/close_service_spec.rb | 2 +- spec/services/issues/move_service_spec.rb | 6 +- spec/services/issues/update_service_spec.rb | 28 +++---- spec/services/merge_requests/close_service_spec.rb | 2 +- spec/services/merge_requests/merge_service_spec.rb | 2 +- .../merge_when_build_succeeds_service_spec.rb | 4 +- .../merge_requests/refresh_service_spec.rb | 16 ++-- .../services/merge_requests/reopen_service_spec.rb | 2 +- .../services/merge_requests/update_service_spec.rb | 24 +++--- spec/services/system_note_service_spec.rb | 90 ++++++++++++++++------ 23 files changed, 169 insertions(+), 124 deletions(-) create mode 100644 changelogs/unreleased/rephrase-system-notes.yml diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ecdcbf08ee1..9a7af5730d2 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -171,7 +171,6 @@ class NotificationService return true unless note.noteable_type.present? # ignore gitlab service messages - return true if note.note.start_with?('Status changed to closed') return true if note.cross_reference? && note.system? target = note.noteable diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1ce66d50368..a33845848b4 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -21,7 +21,7 @@ module SystemNoteService total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) - body = "Added #{commits_text}:\n\n" + body = "added #{commits_text}\n\n" body << existing_commit_summary(noteable, existing_commits, oldrev) body << new_commit_summary(new_commits).join("\n") body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" @@ -38,13 +38,13 @@ module SystemNoteService # # Example Note text: # - # "Assignee removed" + # "removed assignee" # - # "Reassigned to @rspeicher" + # "assigned to @rspeicher" # # Returns the created Note object def change_assignee(noteable, project, author, assignee) - body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}" + body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -59,11 +59,11 @@ module SystemNoteService # # Example Note text: # - # "Added ~1 and removed ~2 ~3 labels" + # "added ~1 and removed ~2 ~3 labels" # - # "Added ~4 label" + # "added ~4 label" # - # "Removed ~5 label" + # "removed ~5 label" # # Returns the created Note object def change_label(noteable, project, author, added_labels, removed_labels) @@ -85,7 +85,6 @@ module SystemNoteService end body << ' ' << 'label'.pluralize(labels_count) - body = body.capitalize create_note(noteable: noteable, project: project, author: author, note: body) end @@ -99,14 +98,13 @@ module SystemNoteService # # Example Note text: # - # "Milestone removed" + # "removed milestone" # - # "Miletone changed to 7.11" + # "changed milestone to 7.11" # # Returns the created Note object def change_milestone(noteable, project, author, milestone) - body = 'Milestone ' - body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}" + body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -121,46 +119,46 @@ module SystemNoteService # # Example Note text: # - # "Status changed to merged" + # "merged" # - # "Status changed to closed by bc17db76" + # "closed via bc17db76" # # Returns the created Note object def change_status(noteable, project, author, status, source) - body = "Status changed to #{status}" - body << " by #{source.gfm_reference(project)}" if source + body = status.dup + body << " via #{source.gfm_reference(project)}" if source create_note(noteable: noteable, project: project, author: author, note: body) end # Called when 'merge when build succeeds' is executed def merge_when_build_succeeds(noteable, project, author, last_commit) - body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" + body = "enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when 'merge when build succeeds' is canceled def cancel_merge_when_build_succeeds(noteable, project, author) - body = 'Canceled the automatic merge' + body = 'canceled the automatic merge' create_note(noteable: noteable, project: project, author: author, note: body) end def remove_merge_request_wip(noteable, project, author) - body = 'Unmarked this merge request as a Work In Progress' + body = 'unmarked as a Work In Progress' create_note(noteable: noteable, project: project, author: author, note: body) end def add_merge_request_wip(noteable, project, author) - body = 'Marked this merge request as a **Work In Progress**' + body = 'marked as a **Work In Progress**' create_note(noteable: noteable, project: project, author: author, note: body) end def self.resolve_all_discussions(merge_request, project, author) - body = "Resolved all discussions" + body = "resolved all discussions" create_note(noteable: merge_request, project: project, author: author, note: body) end @@ -174,7 +172,7 @@ module SystemNoteService # # Example Note text: # - # "Title changed from **Old** to **New**" + # "changed title from **Old** to **New**" # # Returns the created Note object def change_title(noteable, project, author, old_title) @@ -185,7 +183,7 @@ module SystemNoteService marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true) marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true) - body = "Changed title: **#{marked_old_title}** → **#{marked_new_title}**" + body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -197,11 +195,11 @@ module SystemNoteService # # Example Note text: # - # "Made the issue confidential" + # "made the issue confidential" # # Returns the created Note object def change_issue_confidentiality(issue, project, author) - body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible' + body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone' create_note(noteable: issue, project: project, author: author, note: body) end @@ -216,11 +214,11 @@ module SystemNoteService # # Example Note text: # - # "Target branch changed from `Old` to `New`" + # "changed target branch from `Old` to `New`" # # Returns the created Note object def change_branch(noteable, project, author, branch_type, old_branch, new_branch) - body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize + body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -235,7 +233,7 @@ module SystemNoteService # # Example Note text: # - # "Restored target branch `feature`" + # "restored target branch `feature`" # # Returns the created Note object def change_branch_presence(noteable, project, author, branch_type, branch, presence) @@ -246,18 +244,18 @@ module SystemNoteService 'deleted' end - body = "#{verb} #{branch_type} branch `#{branch}`".capitalize + body = "#{verb} #{branch_type} branch `#{branch}`" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when a branch is created from the 'new branch' button on a issue # Example note text: # - # "Started branch `201-issue-branch-button`" + # "created branch `201-issue-branch-button`" def new_issue_branch(issue, project, author, branch) link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) - body = "Started branch [`#{branch}`](#{link})" + body = "created branch [`#{branch}`](#{link})" create_note(noteable: issue, project: project, author: author, note: body) end @@ -269,11 +267,11 @@ module SystemNoteService # # Example Note text: # - # "Mentioned in #1" + # "mentioned in #1" # - # "Mentioned in !2" + # "mentioned in !2" # - # "Mentioned in 54f7727c" + # "mentioned in 54f7727c" # # See cross_reference_note_content. # @@ -303,12 +301,12 @@ module SystemNoteService end def cross_reference?(note_text) - note_text.start_with?(cross_reference_note_prefix) + note_text =~ /\A#{cross_reference_note_prefix}/i end # Check if a cross-reference is disallowed # - # This method prevents adding a "Mentioned in !1" note on every single commit + # This method prevents adding a "mentioned in !1" note on every single commit # in a merge request. Additionally, it prevents the creation of references to # external issues (which would fail). # @@ -370,12 +368,12 @@ module SystemNoteService # # Example Note text: # - # "Soandso marked the task Whatever as completed." + # "marked the task Whatever as completed." # # Returns the created Note object def change_task_status(noteable, project, author, new_task) status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE - body = "Marked the task **#{new_task.source}** as #{status_label}" + body = "marked the task **#{new_task.source}** as #{status_label}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -388,7 +386,7 @@ module SystemNoteService # # Example Note text: # - # "Moved to some_namespace/project_new#11" + # "moved to some_namespace/project_new#11" # # Returns the created Note object def noteable_moved(noteable, project, noteable_ref, author, direction:) @@ -397,7 +395,7 @@ module SystemNoteService end cross_reference = noteable_ref.to_reference(project) - body = "Moved #{direction} #{cross_reference}" + body = "moved #{direction} #{cross_reference}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -405,10 +403,12 @@ module SystemNoteService def notes_for_mentioner(mentioner, noteable, notes) if mentioner.is_a?(Commit) - notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}") + text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}" + notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize) else gfm_reference = mentioner.gfm_reference(noteable.project) - notes.where(note: cross_reference_note_content(gfm_reference)) + text = cross_reference_note_content(gfm_reference) + notes.where(note: [text, text.capitalize]) end end @@ -417,7 +417,7 @@ module SystemNoteService end def cross_reference_note_prefix - 'Mentioned in ' + 'mentioned in ' end def cross_reference_note_content(gfm_reference) diff --git a/changelogs/unreleased/rephrase-system-notes.yml b/changelogs/unreleased/rephrase-system-notes.yml new file mode 100644 index 00000000000..e77c3a31cb4 --- /dev/null +++ b/changelogs/unreleased/rephrase-system-notes.yml @@ -0,0 +1,4 @@ +--- +title: Rephrase some system notes to be compatible with new system note style +merge_request: 7692 +author: diff --git a/doc/api/notes.md b/doc/api/notes.md index 58d40eecf3e..9971806be56 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -21,7 +21,7 @@ Parameters: [ { "id": 302, - "body": "Status changed to closed", + "body": "closed", "attachment": null, "author": { "id": 1, diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index f728d243cdc..d2fa8cd39af 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -515,7 +515,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see new target branch changes' do expect(page).to have_content 'Request to merge fix into feature' - expect(page).to have_content 'Target branch changed from merge-test to feature' + expect(page).to have_content 'changed target branch from merge-test to feature' wait_for_ajax end diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index df9845ba569..aa666a954bc 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -179,7 +179,7 @@ module SharedIssuable project = Project.find_by(name: from_project_name) expect(page).to have_content(user_name) - expect(page).to have_content("Mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") + expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") end def expect_sidebar_content(content) diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 7c5f33c63b8..6d30d085056 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -31,7 +31,7 @@ describe Projects::MilestonesController do # Check system note left for milestone removal last_note = project.issues.find(issue.id).notes[-1].note - expect(last_note).to eq('Milestone removed') + expect(last_note).to eq('removed milestone') end end end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 055210399a7..c9bec05a9da 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -27,7 +27,7 @@ feature 'issue move to another project' do let!(:mr) { create(:merge_request, source_project: old_project) } let(:new_project) { create(:project) } let(:new_project_search) { create(:project) } - let(:text) { 'Text with !1' } + let(:text) { "Text with #{mr.to_reference}" } let(:cross_reference) { old_project.to_reference } background do @@ -43,8 +43,8 @@ feature 'issue move to another project' do expect(current_url).to include project_path(new_project) - expect(page).to have_content("Text with #{cross_reference}!1") - expect(page).to have_content("Moved from #{cross_reference}#1") + expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}") + expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}") expect(page).to have_content(issue.title) end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index ab901e74617..a4d3053d10c 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -20,12 +20,12 @@ feature 'Start new branch from an issue', feature: true do context "when there is a referenced merge request" do let!(:note) do create(:note, :on_issue, :system, project: project, noteable: issue, - note: "Mentioned in !#{referenced_mr.iid}") + note: "mentioned in #{referenced_mr.to_reference}") end let(:referenced_mr) do create(:merge_request, :simple, source_project: project, target_project: project, - description: "Fixes ##{issue.iid}", author: user) + description: "Fixes #{issue.to_reference}", author: user) end before do diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 8eceaad2457..9ad225e3a1b 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -44,7 +44,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).to have_content "The source branch will not be removed." visit_merge_request(merge_request) # Needed to refresh the page - expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i + expect(page).to have_content /enabled an automatic merge when the build for \h{8} succeeds/i end end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 5d7247e2a62..9fffbb43e87 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -141,7 +141,7 @@ describe 'Comments', feature: true do let(:project2) { create(:project, :private) } let(:issue) { create(:issue, project: project2) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') } - let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "Mentioned in #{issue.to_reference(project)}") } + let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") } it 'shows the system note' do login_as :admin diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index e6b6e7c0634..17a15b12dcb 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -223,7 +223,7 @@ describe Note, models: true do let(:note) do create :note, noteable: ext_issue, project: ext_proj, - note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 0124b7271b3..b71a4c5a56e 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -25,7 +25,7 @@ describe API::API, api: true do let!(:cross_reference_note) do create :note, noteable: ext_issue, project: ext_proj, - note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 4465f22a001..7a54373963e 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -62,7 +62,7 @@ describe Issues::CloseService, services: true do it 'creates system note about issue reassign' do note = issue.notes.last - expect(note.note).to include "Status changed to closed" + expect(note.note).to include "closed" end it 'marks todos as done' do diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index f0ded06b785..c7de0d0c534 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -81,11 +81,11 @@ describe Issues::MoveService, services: true do end it 'adds system note to old issue at the end' do - expect(old_issue.notes.last.note).to match /^Moved to/ + expect(old_issue.notes.last.note).to start_with 'moved to' end it 'adds system note to new issue at the end' do - expect(new_issue.notes.last.note).to match /^Moved from/ + expect(new_issue.notes.last.note).to start_with 'moved from' end it 'closes old issue' do @@ -151,7 +151,7 @@ describe Issues::MoveService, services: true do end it 'adds a system note about move after rewritten notes' do - expect(system_notes.last.note).to match /^Moved from/ + expect(system_notes.last.note).to match /^moved from/ end it 'preserves orignal author of comment' do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 4777a90639e..4c878d748c0 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -91,24 +91,24 @@ describe Issues::UpdateService, services: true do end it 'creates system note about issue reassign' do - note = find_note('Reassigned to') + note = find_note('assigned to') expect(note).not_to be_nil - expect(note.note).to include "Reassigned to \@#{user2.username}" + expect(note.note).to include "assigned to #{user2.to_reference}" end it 'creates system note about issue label edit' do - note = find_note('Added ~') + note = find_note('added ~') expect(note).not_to be_nil - expect(note.note).to include "Added ~#{label.id} label" + expect(note.note).to include "added #{label.to_reference} label" end it 'creates system note about title change' do - note = find_note('Changed title:') + note = find_note('changed title') expect(note).not_to be_nil - expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**' + expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**' end end end @@ -128,10 +128,10 @@ describe Issues::UpdateService, services: true do it 'creates system note about confidentiality change' do update_issue(confidential: true) - note = find_note('Made the issue confidential') + note = find_note('made the issue confidential') expect(note).not_to be_nil - expect(note.note).to eq 'Made the issue confidential' + expect(note.note).to eq 'made the issue confidential' end it 'executes confidential issue hooks' do @@ -269,8 +269,8 @@ describe Issues::UpdateService, services: true do before { update_issue(description: "- [x] Task 1\n- [X] Task 2") } it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as completed') - note2 = find_note('Marked the task **Task 2** as completed') + note1 = find_note('marked the task **Task 1** as completed') + note2 = find_note('marked the task **Task 2** as completed') expect(note1).not_to be_nil expect(note2).not_to be_nil @@ -284,8 +284,8 @@ describe Issues::UpdateService, services: true do end it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as incomplete') - note2 = find_note('Marked the task **Task 2** as incomplete') + note1 = find_note('marked the task **Task 1** as incomplete') + note2 = find_note('marked the task **Task 2** as incomplete') expect(note1).not_to be_nil expect(note2).not_to be_nil @@ -299,7 +299,7 @@ describe Issues::UpdateService, services: true do end it 'does not create a system note' do - note = find_note('Marked the task **Task 2** as incomplete') + note = find_note('marked the task **Task 2** as incomplete') expect(note).to be_nil end @@ -312,7 +312,7 @@ describe Issues::UpdateService, services: true do end it 'does not create a system note referencing the position the old item' do - note = find_note('Marked the task **Two** as incomplete') + note = find_note('marked the task **Two** as incomplete') expect(note).to be_nil end diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 24c25e4350f..5f6a7716beb 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -42,7 +42,7 @@ describe MergeRequests::CloseService, services: true do it 'creates system note about merge_request reassign' do note = @merge_request.notes.last - expect(note.note).to include 'Status changed to closed' + expect(note.note).to include 'closed' end it 'marks todos as done' do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 7db32a33c93..dff1781d2aa 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -34,7 +34,7 @@ describe MergeRequests::MergeService, services: true do it 'creates system note about merge_request merge' do note = merge_request.notes.last - expect(note.note).to include 'Status changed to merged' + expect(note.note).to include 'merged' end end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 1f90efdbd6a..c0164138713 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -34,7 +34,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do it 'creates a system note' do note = merge_request.notes.last - expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/ + expect(note.note).to match /enabled an automatic merge when the build for (\w+\/\w+@)?\h{8}/ end end @@ -113,7 +113,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do it 'Posts a system note' do note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'Canceled the automatic merge' + expect(note.note).to include 'canceled the automatic merge' end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index e515bc9f89c..bc340ff9d3c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -76,10 +76,10 @@ describe MergeRequests::RefreshService, services: true do reload_mrs end - it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request.notes.last.note).to include('merged') } it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_merged } - it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + it { expect(@fork_merge_request.notes.last.note).to include('merged') } it { expect(@build_failed_todo).to be_done } it { expect(@fork_build_failed_todo).to be_done } end @@ -95,11 +95,11 @@ describe MergeRequests::RefreshService, services: true do reload_mrs end - it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request.notes.last.note).to include('merged') } it { expect(@merge_request).to be_merged } it { expect(@merge_request.diffs.size).to be > 0 } it { expect(@fork_merge_request).to be_merged } - it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + it { expect(@fork_merge_request.notes.last.note).to include('merged') } it { expect(@build_failed_todo).to be_done } it { expect(@fork_build_failed_todo).to be_done } end @@ -119,7 +119,7 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('Added 28 commits') } + it { expect(@fork_merge_request.notes.last.note).to include('added 28 commits') } it { expect(@fork_merge_request).to be_open } it { expect(@build_failed_todo).to be_pending } it { expect(@fork_build_failed_todo).to be_pending } @@ -146,7 +146,7 @@ describe MergeRequests::RefreshService, services: true do reload_mrs end - it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request.notes.last.note).to include('merged') } it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } @@ -169,8 +169,8 @@ describe MergeRequests::RefreshService, services: true do expect(@merge_request).to be_open notes = @fork_merge_request.notes.reorder(:created_at).map(&:note) - expect(notes[0]).to include('Restored source branch `master`') - expect(notes[1]).to include('Added 28 commits') + expect(notes[0]).to include('restored source branch `master`') + expect(notes[1]).to include('added 28 commits') expect(@fork_merge_request).to be_open end end diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index af7424a76a9..a99d4eac9bd 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -41,7 +41,7 @@ describe MergeRequests::ReopenService, services: true do it 'creates system note about merge_request reopen' do note = merge_request.notes.last - expect(note.note).to include 'Status changed to reopened' + expect(note.note).to include 'reopened' end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index cb5d7cdb467..0bd6db1810a 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -79,31 +79,31 @@ describe MergeRequests::UpdateService, services: true do end it 'creates system note about merge_request reassign' do - note = find_note('Reassigned to') + note = find_note('assigned to') expect(note).not_to be_nil - expect(note.note).to include "Reassigned to \@#{user2.username}" + expect(note.note).to include "assigned to #{user2.to_reference}" end it 'creates system note about merge_request label edit' do - note = find_note('Added ~') + note = find_note('added ~') expect(note).not_to be_nil - expect(note.note).to include "Added ~#{label.id} label" + expect(note.note).to include "added #{label.to_reference} label" end it 'creates system note about title change' do - note = find_note('Changed title:') + note = find_note('changed title') expect(note).not_to be_nil - expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**' + expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**' end it 'creates system note about branch change' do - note = find_note('Target') + note = find_note('changed target') expect(note).not_to be_nil - expect(note.note).to eq 'Target branch changed from `master` to `target`' + expect(note.note).to eq 'changed target branch from `master` to `target`' end context 'when not including source branch removal options' do @@ -258,8 +258,8 @@ describe MergeRequests::UpdateService, services: true do before { update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) } it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as completed') - note2 = find_note('Marked the task **Task 2** as completed') + note1 = find_note('marked the task **Task 1** as completed') + note2 = find_note('marked the task **Task 2** as completed') expect(note1).not_to be_nil expect(note2).not_to be_nil @@ -273,8 +273,8 @@ describe MergeRequests::UpdateService, services: true do end it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as incomplete') - note2 = find_note('Marked the task **Task 2** as incomplete') + note1 = find_note('marked the task **Task 1** as incomplete') + note2 = find_note('marked the task **Task 2** as incomplete') expect(note1).not_to be_nil expect(note2).not_to be_nil diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 2a5709c6322..4a8f6c321aa 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -50,7 +50,7 @@ describe SystemNoteService, services: true do context 'without existing commits' do it 'adds a message header' do - expect(note_lines[0]).to eq "Added #{new_commits.size} commits:" + expect(note_lines[0]).to eq "added #{new_commits.size} commits" end it 'adds a message line for each commit' do @@ -120,7 +120,7 @@ describe SystemNoteService, services: true do context 'when assignee added' do it 'sets the note text' do - expect(subject.note).to eq "Reassigned to @#{assignee.username}" + expect(subject.note).to eq "assigned to @#{assignee.username}" end end @@ -128,7 +128,7 @@ describe SystemNoteService, services: true do let(:assignee) { nil } it 'sets the note text' do - expect(subject.note).to eq 'Assignee removed' + expect(subject.note).to eq 'removed assignee' end end end @@ -147,7 +147,7 @@ describe SystemNoteService, services: true do let(:removed) { [] } it 'sets the note text' do - expect(subject.note).to eq "Added ~#{labels[0].id} ~#{labels[1].id} labels" + expect(subject.note).to eq "added ~#{labels[0].id} ~#{labels[1].id} labels" end end @@ -156,7 +156,7 @@ describe SystemNoteService, services: true do let(:removed) { labels } it 'sets the note text' do - expect(subject.note).to eq "Removed ~#{labels[0].id} ~#{labels[1].id} labels" + expect(subject.note).to eq "removed ~#{labels[0].id} ~#{labels[1].id} labels" end end @@ -165,7 +165,7 @@ describe SystemNoteService, services: true do let(:removed) { [labels[1]] } it 'sets the note text' do - expect(subject.note).to eq "Added ~#{labels[0].id} and removed ~#{labels[1].id} labels" + expect(subject.note).to eq "added ~#{labels[0].id} and removed ~#{labels[1].id} labels" end end end @@ -179,7 +179,7 @@ describe SystemNoteService, services: true do context 'when milestone added' do it 'sets the note text' do - expect(subject.note).to eq "Milestone changed to #{milestone.to_reference}" + expect(subject.note).to eq "changed milestone to #{milestone.to_reference}" end end @@ -187,7 +187,7 @@ describe SystemNoteService, services: true do let(:milestone) { nil } it 'sets the note text' do - expect(subject.note).to eq 'Milestone removed' + expect(subject.note).to eq 'removed milestone' end end end @@ -204,13 +204,13 @@ describe SystemNoteService, services: true do let(:source) { double('commit', gfm_reference: 'commit 123456') } it 'sets the note text' do - expect(subject.note).to eq "Status changed to #{status} by commit 123456" + expect(subject.note).to eq "#{status} via commit 123456" end end context 'without a source' do it 'sets the note text' do - expect(subject.note).to eq "Status changed to #{status}" + expect(subject.note).to eq status end end end @@ -226,7 +226,7 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-f]{40} succeeds/ + expect(subject.note).to match /enabled an automatic merge when the build for (\w+\/\w+@)?\h{40} succeeds/ end end @@ -240,7 +240,7 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to eq "Canceled the automatic merge" + expect(subject.note).to eq "canceled the automatic merge" end end @@ -252,7 +252,7 @@ describe SystemNoteService, services: true do it 'sets the note text' do expect(subject.note). - to eq "Changed title: **{-Old title-}** → **{+#{noteable.title}+}**" + to eq "changed title from **{-Old title-}** to **{+#{noteable.title}+}**" end end end @@ -264,7 +264,7 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' it 'sets the note text' do - expect(subject.note).to eq 'Made the issue visible' + expect(subject.note).to eq 'made the issue visible to everyone' end end end @@ -278,7 +278,7 @@ describe SystemNoteService, services: true do context 'when target branch name changed' do it 'sets the note text' do - expect(subject.note).to eq "Target branch changed from `#{old_branch}` to `#{new_branch}`" + expect(subject.note).to eq "changed target branch from `#{old_branch}` to `#{new_branch}`" end end end @@ -290,7 +290,7 @@ describe SystemNoteService, services: true do context 'when source branch deleted' do it 'sets the note text' do - expect(subject.note).to eq "Deleted source branch `feature`" + expect(subject.note).to eq "deleted source branch `feature`" end end end @@ -302,7 +302,7 @@ describe SystemNoteService, services: true do context 'when a branch is created from the new branch button' do it 'sets the note text' do - expect(subject.note).to match /\AStarted branch [`1-mepmep`]/ + expect(subject.note).to match /\Acreated branch [`1-mepmep`]/ end end end @@ -338,13 +338,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project2.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference(project)}" + expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference(project)}" + expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}" end end end @@ -354,13 +354,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference}" + expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference}" + expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}" end end end @@ -370,7 +370,11 @@ describe SystemNoteService, services: true do describe '.cross_reference?' do it 'is truthy when text begins with expected text' do - expect(described_class.cross_reference?('Mentioned in something')).to be_truthy + expect(described_class.cross_reference?('mentioned in something')).to be_truthy + end + + it 'is truthy when text begins with legacy capitalized expected text' do + expect(described_class.cross_reference?('mentioned in something')).to be_truthy end it 'is falsey when text does not begin with expected text' do @@ -433,6 +437,19 @@ describe SystemNoteService, services: true do expect(described_class.cross_reference_exists?(noteable, commit1)). to be_falsey end + + context 'legacy capitalized cross reference' do + before do + # Mention issue (noteable) from commit0 + system_note = described_class.cross_reference(noteable, commit0, author) + system_note.update(note: system_note.note.capitalize) + end + + it 'is truthy when already mentioned' do + expect(described_class.cross_reference_exists?(noteable, commit0)). + to be_truthy + end + end end context 'commit from commit' do @@ -450,6 +467,19 @@ describe SystemNoteService, services: true do expect(described_class.cross_reference_exists?(commit1, commit0)). to be_falsey end + + context 'legacy capitalized cross reference' do + before do + # Mention commit1 from commit0 + system_note = described_class.cross_reference(commit0, commit1, author) + system_note.update(note: system_note.note.capitalize) + end + + it 'is truthy when already mentioned' do + expect(described_class.cross_reference_exists?(commit0, commit1)). + to be_truthy + end + end end context 'commit with cross-reference from fork' do @@ -465,6 +495,18 @@ describe SystemNoteService, services: true do expect(described_class.cross_reference_exists?(noteable, commit2)). to be true end + + context 'legacy capitalized cross reference' do + before do + system_note = described_class.cross_reference(noteable, commit0, author2) + system_note.update(note: system_note.note.capitalize) + end + + it 'is true when a fork mentions an external issue' do + expect(described_class.cross_reference_exists?(noteable, commit2)). + to be true + end + end end end @@ -498,7 +540,7 @@ describe SystemNoteService, services: true do it_behaves_like 'cross project mentionable' it 'notifies about noteable being moved to' do - expect(subject.note).to match /Moved to/ + expect(subject.note).to match /moved to/ end end @@ -508,7 +550,7 @@ describe SystemNoteService, services: true do it_behaves_like 'cross project mentionable' it 'notifies about noteable being moved from' do - expect(subject.note).to match /Moved from/ + expect(subject.note).to match /moved from/ end end -- cgit v1.2.1 From 058c652904b13cea73e92615776f29fd1a8a1ded Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 24 Nov 2016 10:32:55 +0000 Subject: Fixed issue boards issue sorting when dragging issue into list Currently it just appends the new issue to the end of list & then sorts by priority which can cause some strange effects. For example if you drag the issue to the top of the list & then vue re-renders, the issue actually goes to the bottom. --- app/assets/javascripts/boards/components/board_list.js.es6 | 2 +- app/assets/javascripts/boards/models/list.js.es6 | 8 ++++++-- app/assets/javascripts/boards/stores/boards_store.js.es6 | 4 ++-- changelogs/unreleased/boards-issue-sorting.yml | 4 ++++ 4 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/boards-issue-sorting.yml diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 8e91cbfac75..8996ca438a6 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -94,7 +94,7 @@ gl.issueBoards.onStart(); }, onAdd: (e) => { - gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); + gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex); this.$nextTick(() => { e.item.remove(); diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 8a7dc67409e..429bd27c3fb 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -106,9 +106,13 @@ class List { }); } - addIssue (issue, listFrom) { + addIssue (issue, listFrom, newIndex) { if (!this.findIssue(issue.id)) { - this.issues.push(issue); + if (newIndex !== undefined) { + this.issues.splice(newIndex, 0, issue); + } else { + this.issues.push(issue); + } if (this.label) { issue.addLabel(this.label); diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index 6bc95aa60f2..bb2a4de8b18 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -89,14 +89,14 @@ }); listFrom.update(); }, - moveIssueToList (listFrom, listTo, issue) { + moveIssueToList (listFrom, listTo, issue, newIndex) { const issueTo = listTo.findIssue(issue.id), issueLists = issue.getLists(), listLabels = issueLists.map( listIssue => listIssue.label ); // Add to new lists issues if it doesn't already exist if (!issueTo) { - listTo.addIssue(issue, listFrom); + listTo.addIssue(issue, listFrom, newIndex); } if (listTo.type === 'done' && listFrom.type !== 'backlog') { diff --git a/changelogs/unreleased/boards-issue-sorting.yml b/changelogs/unreleased/boards-issue-sorting.yml new file mode 100644 index 00000000000..fb7dc2f9190 --- /dev/null +++ b/changelogs/unreleased/boards-issue-sorting.yml @@ -0,0 +1,4 @@ +--- +title: Fixed issue boards issue sorting when dragging issue into list +merge_request: +author: -- cgit v1.2.1 From 18a54116e2f1d2ac88d03ad7ed48213dab580854 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 24 Nov 2016 10:38:24 +0000 Subject: Fixed dragging issue moving wrong issue after multiple drags of issue --- app/assets/javascripts/boards/components/board_list.js.es6 | 2 +- app/views/projects/boards/components/_card.html.haml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 8996ca438a6..48e37fa64ea 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -88,8 +88,8 @@ const card = this.$refs.issue[e.oldIndex]; card.showDetail = false; - Store.moving.issue = card.issue; Store.moving.list = card.list; + Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId); gl.issueBoards.onStart(); }, diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 34effac17b2..1f31496e73f 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -1,5 +1,6 @@ %li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }', ":index" => "index", + ":data-issue-id" => "issue.id", "@mousedown" => "mouseDown", "@mousemove" => "mouseMove", "@mouseup" => "showIssue($event)" } -- cgit v1.2.1 From 2f5637d6e39efa4b1a1cd2acdffa478e903ecd98 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Wed, 23 Nov 2016 13:37:42 +0000 Subject: renames some of the specs and adds changelog entry --- .../24779-last-deployment-call-on-nil-environment-fix.yml | 4 ++++ spec/views/projects/builds/show.html.haml_spec.rb | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml diff --git a/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml b/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml new file mode 100644 index 00000000000..5e7580fb8f2 --- /dev/null +++ b/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml @@ -0,0 +1,4 @@ +--- +title: fixes last_deployment call environment is nil +merge_request: 7671 +author: diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 869dcb417e6..745d0c745bd 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -88,7 +88,7 @@ describe 'projects/builds/show', :view do create(:ci_build, :running, environment: 'staging', pipeline: pipeline) end - context 'and environment does exist' do + context 'when environment exists' do let!(:environment) do create(:environment, name: 'staging', project: project) end @@ -101,7 +101,7 @@ describe 'projects/builds/show', :view do '.environment-information', text: expected_text) end - context 'and has deployment' do + context 'when it has deployment' do let!(:deployment) do create(:deployment, environment: environment) end @@ -118,7 +118,7 @@ describe 'projects/builds/show', :view do end end - context 'and environment does not exist' do + context 'when environment does not exist' do it 'shows deployment message' do expected_text = 'This build is creating a deployment to staging' render -- cgit v1.2.1 From 2749c1b5687bb50b980dc7bfc1d3471ea16f1ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 13:05:00 +0100 Subject: Stop supporting Google and Azure as backup strategies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The amount of gems required is quite high compared to the usefulness of the features. Related to !4928, !6713 Signed-off-by: Rémy Coutable --- Gemfile | 2 -- Gemfile.lock | 31 ---------------------- changelogs/unreleased/remove-backup-strategies.yml | 4 +++ doc/raketasks/backup_restore.md | 2 +- 4 files changed, 5 insertions(+), 34 deletions(-) create mode 100644 changelogs/unreleased/remove-backup-strategies.yml diff --git a/Gemfile b/Gemfile index 9e815925a1f..e7f248ee766 100644 --- a/Gemfile +++ b/Gemfile @@ -85,10 +85,8 @@ gem 'dropzonejs-rails', '~> 0.7.1' # for backups gem 'fog-aws', '~> 0.9' -gem 'fog-azure', '~> 0.0' gem 'fog-core', '~> 1.40' gem 'fog-local', '~> 0.3' -gem 'fog-google', '~> 0.3' gem 'fog-openstack', '~> 0.1' gem 'fog-rackspace', '~> 0.1.1' diff --git a/Gemfile.lock b/Gemfile.lock index bdc60552480..3684974a766 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,21 +66,6 @@ GEM descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) - azure (0.7.5) - addressable (~> 2.3) - azure-core (~> 0.1) - faraday (~> 0.9) - faraday_middleware (~> 0.10) - json (~> 1.8) - mime-types (>= 1, < 3.0) - nokogiri (~> 1.6) - systemu (~> 2.6) - thor (~> 0.19) - uuid (~> 2.0) - azure-core (0.1.2) - faraday (~> 0.9) - faraday_middleware (~> 0.10) - nokogiri (~> 1.6) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) @@ -217,19 +202,10 @@ GEM fog-json (~> 1.0) fog-xml (~> 0.1) ipaddress (~> 0.8) - fog-azure (0.0.2) - azure (~> 0.6) - fog-core (~> 1.27) - fog-json (~> 1.0) - fog-xml (~> 0.1) fog-core (1.42.0) builder excon (~> 0.49) formatador (~> 0.2) - fog-google (0.3.2) - fog-core - fog-json - fog-xml fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) @@ -397,8 +373,6 @@ GEM rb-inotify (>= 0.9) loofah (2.0.3) nokogiri (>= 1.5.9) - macaddr (1.7.1) - systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) mail_room (0.9.0) @@ -728,7 +702,6 @@ GEM sys-filesystem (1.1.6) ffi sysexits (1.2.0) - systemu (2.6.5) teaspoon (1.1.5) railties (>= 3.2.5, < 6) teaspoon-jasmine (2.2.0) @@ -768,8 +741,6 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) - uuid (2.3.8) - macaddr (~> 1.0) version_sorter (2.1.0) virtus (1.0.5) axiom-types (~> 0.1) @@ -849,9 +820,7 @@ DEPENDENCIES ffaker (~> 2.0.0) flay (~> 2.6.1) fog-aws (~> 0.9) - fog-azure (~> 0.0) fog-core (~> 1.40) - fog-google (~> 0.3) fog-local (~> 0.3) fog-openstack (~> 0.1) fog-rackspace (~> 0.1.1) diff --git a/changelogs/unreleased/remove-backup-strategies.yml b/changelogs/unreleased/remove-backup-strategies.yml new file mode 100644 index 00000000000..9f034613c2c --- /dev/null +++ b/changelogs/unreleased/remove-backup-strategies.yml @@ -0,0 +1,4 @@ +--- +title: Stop supporting Google and Azure as backup strategies +merge_request: +author: diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 7484bc2295e..17485b11c09 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -88,7 +88,7 @@ It uses the [Fog library](http://fog.io/) to perform the upload. In the example below we use Amazon S3 for storage, but Fog also lets you use [other storage providers](http://fog.io/storage/). GitLab [imports cloud drivers](https://gitlab.com/gitlab-org/gitlab-ce/blob/30f5b9a5b711b46f1065baf755e413ceced5646b/Gemfile#L88) -for AWS, Azure, Google, OpenStack Swift and Rackspace as well. A local driver is +for AWS, OpenStack Swift and Rackspace as well. A local driver is [also available](#uploading-to-locally-mounted-shares). For omnibus packages: -- cgit v1.2.1 From 304163becba3610a99dfff644c13972a2f54ed3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 13:22:38 +0100 Subject: API: Use `#find_project` in API::Triggers and API::Services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/services.rb | 2 +- lib/api/triggers.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/services.rb b/lib/api/services.rb index 4d23499aa39..bc427705777 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -65,7 +65,7 @@ module API detail 'Added in GitLab 8.13' end post ':id/services/:service_slug/trigger' do - project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + project = find_project(params[:id]) # This is not accurate, but done to prevent leakage of the project names not_found!('Service') unless project diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 569598fbd2c..bb4de39def1 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -13,7 +13,7 @@ module API optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end post ":id/(ref/:ref/)trigger/builds" do - project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + project = find_project(params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger unauthorized! unless trigger.project == project -- cgit v1.2.1 From 9dfbfbb2d10dfc6297acff1b59dfbaf43b848d96 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 24 Nov 2016 13:30:53 +0100 Subject: Don't convert data which already is the target type --- lib/api/commit_statuses.rb | 2 +- lib/api/commits.rb | 2 +- lib/api/groups.rb | 2 +- lib/api/variables.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index f54d4f06627..492884d162b 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -77,7 +77,7 @@ module API ) begin - case params[:state].to_s + case params[:state] when 'pending' status.enqueue! when 'running' diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 0319d076ecb..2670a2d413a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -48,7 +48,7 @@ module API requires :id, type: Integer, desc: 'The project ID' requires :branch_name, type: String, desc: 'The name of branch' requires :commit_message, type: String, desc: 'Commit message' - requires :actions, type: Array, desc: 'Actions to perform in commit' + requires :actions, type: Array[Hash], desc: 'Actions to perform in commit' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 48ad3b80ae0..fc39fdf4b67 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -33,7 +33,7 @@ module API groups = groups.search(params[:search]) if params[:search].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? - groups = groups.reorder(params[:order_by] => params[:sort].to_sym) + groups = groups.reorder(params[:order_by] => params[:sort]) present paginate(groups), with: Entities::Group end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 90f904b8a12..f623b1dfe9f 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -30,7 +30,7 @@ module API end get ':id/variables/:key' do key = params[:key] - variable = user_project.variables.find_by(key: key.to_s) + variable = user_project.variables.find_by(key: key) return not_found!('Variable') unless variable -- cgit v1.2.1 From 4f5ed812325845f263fc9b566651c1179b5c24bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 14:40:35 +0100 Subject: API: Introduce `#find_project!` which also check access permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/helpers.rb | 17 ++++++++++------- lib/api/projects.rb | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 60067758e95..42f4c2ccf9d 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -68,7 +68,7 @@ module API end def user_project - @project ||= find_project(params[:id]) + @project ||= find_project!(params[:id]) end def available_labels @@ -76,12 +76,15 @@ module API end def find_project(id) - project = - if id =~ /^\d+$/ - Project.find_by(id: id) - else - Project.find_with_namespace(id) - end + if id =~ /^\d+$/ + Project.find_by(id: id) + else + Project.find_with_namespace(id) + end + end + + def find_project!(id) + project = find_project(id) if can?(current_user, :read_project, project) project diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ddfde178d30..2ea3c433ae2 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -379,7 +379,7 @@ module API # POST /projects/:id/fork/:forked_from_id post ":id/fork/:forked_from_id" do authenticated_as_admin! - forked_from_project = find_project(params[:forked_from_id]) + forked_from_project = find_project!(params[:forked_from_id]) unless forked_from_project.nil? if user_project.forked_from_project.nil? user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) -- cgit v1.2.1 From 28f4d7aa28c6ce2765fce7922058c82f7633dae3 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 24 Nov 2016 15:29:22 +0100 Subject: You can only assign default_branch when editing a project or when creating a project for a specified user [ci skip] You can only assign default_branch when editing a project [ci skip] --- doc/api/projects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index b02a901d884..bd27a0a6fae 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -592,7 +592,6 @@ Parameters: | `name` | string | yes | The name of the new project | | `path` | string | no | Custom repository name for new project. By default generated based on name | | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | -| `default_branch` | string | no | `master` by default | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | | `merge_requests_enabled` | boolean | no | Enable merge requests for this project | @@ -625,6 +624,7 @@ Parameters: | `user_id` | integer | yes | The user ID of the project owner | | `name` | string | yes | The name of the new project | | `path` | string | no | Custom repository name for new project. By default generated based on name | +| `default_branch` | string | no | `master` by default | | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | -- cgit v1.2.1 From 785d5c8ed15edbabb5704072051569d8db61f4a1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 24 Nov 2016 15:58:31 +0100 Subject: Create pipeline along with builds in the transation --- app/services/ci/create_pipeline_service.rb | 12 +++++++++--- app/services/ci/process_pipeline_service.rb | 9 --------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index cde856b0186..e3bc9847200 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -45,9 +45,15 @@ module Ci return error('No builds for this pipeline.') end - pipeline.save - pipeline.process! - pipeline + Ci::Pipeline.transaction do + pipeline.save + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end + + pipeline.tap(&:process!) end private diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 8face432d97..e6bd1d1460c 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -5,11 +5,6 @@ module Ci def execute(pipeline) @pipeline = pipeline - # This method will ensure that our pipeline does have all builds for all stages created - if created_builds.empty? - create_builds! - end - new_builds = stage_indexes_of_created_builds.map do |index| process_stage(index) @@ -22,10 +17,6 @@ module Ci private - def create_builds! - Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline) - end - def process_stage(index) current_status = status_for_prior_stages(index) -- cgit v1.2.1 From 81ba3f9177fcfd76f6b3b715c572ce4920398345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 16:58:32 +0100 Subject: API: Introduce `#find_group!` which also check access permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/groups.rb | 8 ++++---- lib/api/helpers.rb | 10 +++++++++- lib/api/helpers/members_helpers.rb | 2 +- lib/api/issues.rb | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 48ad3b80ae0..a3489c4eb92 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -82,7 +82,7 @@ module API :lfs_enabled, :request_access_enabled end put ':id' do - group = find_group(params[:id]) + group = find_group!(params[:id]) authorize! :admin_group, group if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute @@ -96,13 +96,13 @@ module API success Entities::GroupDetail end get ":id" do - group = find_group(params[:id]) + group = find_group!(params[:id]) present group, with: Entities::GroupDetail end desc 'Remove a group.' delete ":id" do - group = find_group(params[:id]) + group = find_group!(params[:id]) authorize! :admin_group, group DestroyGroupService.new(group, current_user).execute end @@ -111,7 +111,7 @@ module API success Entities::Project end get ":id/projects" do - group = find_group(params[:id]) + group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) projects = paginate projects present projects, with: Entities::Project, user: current_user diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 42f4c2ccf9d..0d3ddb89dc3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -105,7 +105,15 @@ module API end def find_group(id) - group = Group.find_by(path: id) || Group.find_by(id: id) + if id =~ /^\d+$/ + Group.find_by(id: id) + else + Group.find_by(path: id) + end + end + + def find_group!(id) + group = find_group(id) if can?(current_user, :read_group, group) group diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 90114f6f667..d9cae1501f8 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -2,7 +2,7 @@ module API module Helpers module MembersHelpers def find_source(source_type, id) - public_send("find_#{source_type}", id) + public_send("find_#{source_type}!", id) end def authorize_admin_source!(source_type, source) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index eea5b91d4f9..2fea71870b8 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -68,7 +68,7 @@ module API # GET /groups/:id/issues?milestone=1.0.0 # GET /groups/:id/issues?milestone=1.0.0&state=closed get ":id/issues" do - group = find_group(params[:id]) + group = find_group!(params[:id]) params[:state] ||= 'opened' params[:group_id] = group.id -- cgit v1.2.1 From 141b4d28c615db0a3e25868efd47fd0f2946fdbe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 24 Nov 2016 16:54:24 +0000 Subject: Fixed issue boards scrolling with a lot of lists & issues When a board has a lot of lists & issues scrolling stops the user from moving the issue to the lsat list (or any list not on screen). This changes that by making the scrollable element the board-list element. This will need re-thinking when sorting in lists is possible. --- app/assets/javascripts/boards/components/board_list.js.es6 | 1 + changelogs/unreleased/issue-boards-scrollable-element.yml | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 changelogs/unreleased/issue-boards-scrollable-element.yml diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 8e91cbfac75..1e2bf445d75 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -80,6 +80,7 @@ }, mounted () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ + scroll: document.querySelectorAll('.boards-list')[0], group: 'issues', sort: false, disabled: this.disabled, diff --git a/changelogs/unreleased/issue-boards-scrollable-element.yml b/changelogs/unreleased/issue-boards-scrollable-element.yml new file mode 100644 index 00000000000..90edc30e791 --- /dev/null +++ b/changelogs/unreleased/issue-boards-scrollable-element.yml @@ -0,0 +1,4 @@ +--- +title: Fixed issue boards scrolling with a lot of lists & issues +merge_request: +author: -- cgit v1.2.1 From ed61d44e1edfd41c36ec9085aa95f470bb5699fa Mon Sep 17 00:00:00 2001 From: Luis Alonso Chavez Armendariz Date: Thu, 24 Nov 2016 10:28:52 -0700 Subject: Remove unnecessary sentences for status codes in the API documentation --- changelogs/unreleased/issue-24534.yml | 4 ++++ doc/api/access_requests.md | 8 ------- doc/api/award_emoji.md | 4 ++-- doc/api/boards.md | 10 -------- doc/api/branches.md | 7 +----- doc/api/broadcast_messages.md | 4 ---- doc/api/issues.md | 31 ++++++------------------- doc/api/labels.md | 25 ++++---------------- doc/api/members.md | 10 -------- doc/api/merge_requests.md | 43 +++++++++++------------------------ doc/api/notes.md | 9 +++----- doc/api/projects.md | 19 +++------------- doc/api/system_hooks.md | 3 +-- doc/api/tags.md | 21 ++++++----------- doc/api/users.md | 8 ++----- 15 files changed, 48 insertions(+), 158 deletions(-) create mode 100644 changelogs/unreleased/issue-24534.yml diff --git a/changelogs/unreleased/issue-24534.yml b/changelogs/unreleased/issue-24534.yml new file mode 100644 index 00000000000..14d6730d3f6 --- /dev/null +++ b/changelogs/unreleased/issue-24534.yml @@ -0,0 +1,4 @@ +--- +title: Remove unnecessary sentences for status codes in the API documentation +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md index ea308b54d62..dee3e384080 100644 --- a/doc/api/access_requests.md +++ b/doc/api/access_requests.md @@ -18,8 +18,6 @@ Gets a list of access requests viewable by the authenticated user. -Returns `200` if the request succeeds. - ``` GET /groups/:id/access_requests GET /projects/:id/access_requests @@ -61,8 +59,6 @@ Example response: Requests access for the authenticated user to a group or project. -Returns `201` if the request succeeds. - ``` POST /groups/:id/access_requests POST /projects/:id/access_requests @@ -94,8 +90,6 @@ Example response: Approves an access request for the given user. -Returns `201` if the request succeeds. - ``` PUT /groups/:id/access_requests/:user_id/approve PUT /projects/:id/access_requests/:user_id/approve @@ -129,8 +123,6 @@ Example response: Denies an access request for the given user. -Returns `200` if the request succeeds. - ``` DELETE /groups/:id/access_requests/:user_id DELETE /projects/:id/access_requests/:user_id diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md index 06111f4ab67..58092bdd400 100644 --- a/doc/api/award_emoji.md +++ b/doc/api/award_emoji.md @@ -158,7 +158,7 @@ Example Response: ### Delete an award emoji Sometimes its just not meant to be, and you'll have to remove your award. Only available to -admins or the author of the award. Status code 200 on success, 401 if unauthorized. +admins or the author of the award. ``` DELETE /projects/:id/issues/:issue_id/award_emoji/:award_id @@ -331,7 +331,7 @@ Example Response: ### Delete an award emoji Sometimes its just not meant to be, and you'll have to remove your award. Only available to -admins or the author of the award. Status code 200 on success, 401 if unauthorized. +admins or the author of the award. ``` DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id diff --git a/doc/api/boards.md b/doc/api/boards.md index 28681719f43..c83db6df80c 100644 --- a/doc/api/boards.md +++ b/doc/api/boards.md @@ -148,10 +148,6 @@ Example response: Creates a new Issue Board list. -If the operation is successful, a status code of `200` and the newly-created -list is returned. If an error occurs, an error number and a message explaining -the reason is returned. - ``` POST /projects/:id/boards/:board_id/lists ``` @@ -184,10 +180,6 @@ Example response: Updates an existing Issue Board list. This call is used to change list position. -If the operation is successful, a code of `200` and the updated board list is -returned. If an error occurs, an error number and a message explaining the -reason is returned. - ``` PUT /projects/:id/boards/:board_id/lists/:list_id ``` @@ -220,8 +212,6 @@ Example response: ## Delete a board list Only for admins and project owners. Soft deletes the board list in question. -If the operation is successful, a status code `200` is returned. In case you cannot -destroy this board list, or it is not present, code `404` is given. ``` DELETE /projects/:id/boards/:board_id/lists/:list_id diff --git a/doc/api/branches.md b/doc/api/branches.md index f68eeb9f86b..07dfa5d4d7f 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -212,9 +212,6 @@ Example response: } ``` -It returns `200` if it succeeds or `400` if failed with an error message -explaining the reason. - ## Delete repository branch ``` @@ -226,8 +223,7 @@ DELETE /projects/:id/repository/branches/:branch | `id` | integer | yes | The ID of a project | | `branch` | string | yes | The name of the branch | -It returns `200` if it succeeds, `404` if the branch to be deleted does not exist -or `400` for other reasons. In case of an error, an explaining message is provided. +In case of an error, an explaining message is provided. ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch" @@ -253,7 +249,6 @@ DELETE /projects/:id/repository/merged_branches | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | -It returns `200` to indicate deletion of all merged branches was started. ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/merged_branches" diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md index c3a9207a3ae..a3e9c01f335 100644 --- a/doc/api/broadcast_messages.md +++ b/doc/api/broadcast_messages.md @@ -62,10 +62,6 @@ Example response: ## Create a broadcast message -Responds with `400 Bad request` when the `message` parameter is missing or the -`color` or `font` values are invalid, and `201 Created` when the broadcast -message was successfully created. - ``` POST /broadcast_messages ``` diff --git a/doc/api/issues.md b/doc/api/issues.md index 134263d27b4..16f8e32c82a 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -315,10 +315,6 @@ Example response: Creates a new project issue. -If the operation is successful, a status code of `200` and the newly-created -issue is returned. If an error occurs, an error number and a message explaining -the reason is returned. - ``` POST /projects/:id/issues ``` @@ -377,10 +373,6 @@ Example response: Updates an existing project issue. This call is also used to mark an issue as closed. -If the operation is successful, a code of `200` and the updated issue is -returned. If an error occurs, an error number and a message explaining the -reason is returned. - ``` PUT /projects/:id/issues/:issue_id ``` @@ -439,8 +431,6 @@ Example response: ## Delete an issue Only for admins and project owners. Soft deletes the issue in question. -If the operation is successful, a status code `200` is returned. In case you cannot -destroy this issue, or it is not present, code `404` is given. ``` DELETE /projects/:id/issues/:issue_id @@ -457,9 +447,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git ## Move an issue -Moves an issue to a different project. If the operation is successful, a status -code `201` together with moved issue is returned. If the project, issue, or -target project is not found, error `404` is returned. If the target project +Moves an issue to a different project. If the target project equals the source project or the user has insufficient permissions to move an issue, error `400` together with an explaining error message is returned. @@ -518,11 +506,9 @@ Example response: ## Subscribe to an issue -Subscribes the authenticated user to an issue to receive notifications. If the -operation is successful, status code `201` together with the updated issue is -returned. If the user is already subscribed to the issue, the status code `304` -is returned. If the project or issue is not found, status code `404` is -returned. +Subscribes the authenticated user to an issue to receive notifications. +If the user is already subscribed to the issue, the status code `304` +is returned. ``` POST /projects/:id/issues/:issue_id/subscription @@ -576,10 +562,8 @@ Example response: ## Unsubscribe from an issue Unsubscribes the authenticated user from the issue to not receive notifications -from it. If the operation is successful, status code `200` together with the -updated issue is returned. If the user is not subscribed to the issue, the -status code `304` is returned. If the project or issue is not found, status code -`404` is returned. +from it. If the user is not subscribed to the issue, the +status code `304` is returned. ``` DELETE /projects/:id/issues/:issue_id/subscription @@ -633,8 +617,7 @@ Example response: ## Create a todo -Manually creates a todo for the current user on an issue. If the request is -successful, status code `200` together with the created todo is returned. If +Manually creates a todo for the current user on an issue. If there already exists a todo for the user on that issue, status code `304` is returned. diff --git a/doc/api/labels.md b/doc/api/labels.md index 78686fdcad4..863b28c23b7 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -82,9 +82,6 @@ Example response: Creates a new label for the given repository with the given name and color. -It returns 200 if the label was successfully created, 400 for wrong parameters -and 409 if the label already exists. - ``` POST /projects/:id/labels ``` @@ -121,10 +118,6 @@ Example response: Deletes a label with a given name. -It returns 200 if the label was successfully deleted, 400 for wrong parameters -and 404 if the label does not exist. -In case of an error, an additional error message is returned. - ``` DELETE /projects/:id/labels ``` @@ -159,10 +152,6 @@ Example response: Updates an existing label with new name or new color. At least one parameter is required, to update the label. -It returns 200 if the label was successfully deleted, 400 for wrong parameters -and 404 if the label does not exist. -In case of an error, an additional error message is returned. - ``` PUT /projects/:id/labels ``` @@ -199,11 +188,9 @@ Example response: ## Subscribe to a label -Subscribes the authenticated user to a label to receive notifications. If the -operation is successful, status code `201` together with the updated label is -returned. If the user is already subscribed to the label, the status code `304` -is returned. If the project or label is not found, status code `404` is -returned. +Subscribes the authenticated user to a label to receive notifications. +If the user is already subscribed to the label, the status code `304` +is returned. ``` POST /projects/:id/labels/:label_id/subscription @@ -237,10 +224,8 @@ Example response: ## Unsubscribe from a label Unsubscribes the authenticated user from a label to not receive notifications -from it. If the operation is successful, status code `200` together with the -updated label is returned. If the user is not subscribed to the label, the -status code `304` is returned. If the project or label is not found, status code -`404` is returned. +from it. If the user is not subscribed to the label, the +status code `304` is returned. ``` DELETE /projects/:id/labels/:label_id/subscription diff --git a/doc/api/members.md b/doc/api/members.md index 6535e9a7801..5dcb2a5f60a 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -16,8 +16,6 @@ The access levels are defined in the `Gitlab::Access` module. Currently, these l Gets a list of group or project members viewable by the authenticated user. -Returns `200` if the request succeeds. - ``` GET /groups/:id/members GET /projects/:id/members @@ -60,8 +58,6 @@ Example response: Gets a member of a group or project. -Returns `200` if the request succeeds. - ``` GET /groups/:id/members/:user_id GET /projects/:id/members/:user_id @@ -95,8 +91,6 @@ Example response: Adds a member to a group or project. -Returns `201` if the request succeeds. - ``` POST /groups/:id/members POST /projects/:id/members @@ -131,8 +125,6 @@ Example response: Updates a member of a group or project. -Returns `200` if the request succeeds. - ``` PUT /groups/:id/members/:user_id PUT /projects/:id/members/:user_id @@ -167,8 +159,6 @@ Example response: Removes a user from a group or project. -Returns `200` if the request succeeds. - ``` DELETE /groups/:id/members/:user_id DELETE /projects/:id/members/:user_id diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index f4167403c2c..1df661369a4 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -337,9 +337,6 @@ Parameters: } ``` -If the operation is successful, 200 and the newly created merge request is returned. -If an error occurs, an error number and a message explaining the reason is returned. - ## Update MR Updates an existing merge request. You can change the target branch, title, or even close the MR. @@ -414,14 +411,9 @@ Parameters: } ``` -If the operation is successful, 200 and the updated merge request is returned. -If an error occurs, an error number and a message explaining the reason is returned. - ## Delete a merge request Only for admins and project owners. Soft deletes the merge request in question. -If the operation is successful, a status code `200` is returned. In case you cannot -destroy this merge request, or it is not present, code `404` is given. ``` DELETE /projects/:id/merge_requests/:merge_request_id @@ -440,15 +432,14 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git Merge changes submitted with MR using this API. -If the merge succeeds you'll get a `200 OK`. -If it has some conflicts and can not be merged - you'll get a 405 and the error message 'Branch cannot be merged' +If it has some conflicts and can not be merged - you'll get a `405` and the error message 'Branch cannot be merged' -If merge request is already merged or closed - you'll get a 406 and the error message 'Method Not Allowed' +If merge request is already merged or closed - you'll get a `406` and the error message 'Method Not Allowed' -If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a 409 and the error message 'SHA does not match HEAD of source branch' +If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a `409` and the error message 'SHA does not match HEAD of source branch' -If you don't have permissions to accept this merge request - you'll get a 401 +If you don't have permissions to accept this merge request - you'll get a `401` ``` PUT /projects/:id/merge_requests/:merge_request_id/merge @@ -520,13 +511,11 @@ Parameters: ## Cancel Merge When Build Succeeds -If successful you'll get `200 OK`. +If you don't have permissions to accept this merge request - you'll get a `401` -If you don't have permissions to accept this merge request - you'll get a 401 +If the merge request is already merged or closed - you get `405` and error message 'Method Not Allowed' -If the merge request is already merged or closed - you get 405 and error message 'Method Not Allowed' - -In case the merge request is not set to be merged when the build succeeds, you'll also get a 406 error. +In case the merge request is not set to be merged when the build succeeds, you'll also get a `406` error. ``` PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds ``` @@ -670,11 +659,8 @@ Example response when an external issue tracker (e.g. JIRA) is used: ## Subscribe to a merge request -Subscribes the authenticated user to a merge request to receive notification. If -the operation is successful, status code `201` together with the updated merge -request is returned. If the user is already subscribed to the merge request, the -status code `304` is returned. If the project or merge request is not found, -status code `404` is returned. +Subscribes the authenticated user to a merge request to receive notification. If the user is already subscribed to the merge request, the +status code `304` is returned. ``` POST /projects/:id/merge_requests/:merge_request_id/subscription @@ -747,10 +733,8 @@ Example response: ## Unsubscribe from a merge request Unsubscribes the authenticated user from a merge request to not receive -notifications from that merge request. If the operation is successful, status -code `200` together with the updated merge request is returned. If the user is -not subscribed to the merge request, the status code `304` is returned. If the -project or merge request is not found, status code `404` is returned. +notifications from that merge request. If the user is +not subscribed to the merge request, the status code `304` is returned. ``` DELETE /projects/:id/merge_requests/:merge_request_id/subscription @@ -822,9 +806,8 @@ Example response: ## Create a todo -Manually creates a todo for the current user on a merge request. If the -request is successful, status code `200` together with the created todo is -returned. If there already exists a todo for the user on that merge request, +Manually creates a todo for the current user on a merge request. +If there already exists a todo for the user on that merge request, status code `304` is returned. ``` diff --git a/doc/api/notes.md b/doc/api/notes.md index 58d40eecf3e..ba20a885fc8 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -109,8 +109,7 @@ Parameters: ### Delete an issue note -Deletes an existing note of an issue. On success, this API method returns 200 -and the deleted note. If the note does not exist, the API returns 404. +Deletes an existing note of an issue. ``` DELETE /projects/:id/issues/:issue_id/notes/:note_id @@ -234,8 +233,7 @@ Parameters: ### Delete a snippet note -Deletes an existing note of a snippet. On success, this API method returns 200 -and the deleted note. If the note does not exist, the API returns 404. +Deletes an existing note of a snippet. ``` DELETE /projects/:id/snippets/:snippet_id/notes/:note_id @@ -364,8 +362,7 @@ Parameters: ### Delete a merge request note -Deletes an existing note of a merge request. On success, this API method returns -200 and the deleted note. If the note does not exist, the API returns 404. +Deletes an existing note of a merge request. ``` DELETE /projects/:id/merge_requests/:merge_request_id/notes/:note_id diff --git a/doc/api/projects.md b/doc/api/projects.md index 467a880ac13..4993448ff25 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -644,7 +644,7 @@ Parameters: ### Edit project -Updates an existing project +Updates an existing project. ``` PUT /projects/:id @@ -674,9 +674,6 @@ Parameters: | `lfs_enabled` | boolean | no | Enable LFS | | `request_access_enabled` | boolean | no | Allow users to request member access | -On success, method returns 200 with the updated project. If parameters are -invalid, 400 is returned. - ### Fork project Forks a project into the user namespace of the authenticated user or the one provided. @@ -694,8 +691,7 @@ Parameters: ### Star a project -Stars a given project. Returns status code `201` and the project on success and -`304` if the project is already starred. +Stars a given project. Returns status code `304` if the project is already starred. ``` POST /projects/:id/star @@ -765,8 +761,7 @@ Example response: ### Unstar a project -Unstars a given project. Returns status code `200` and the project on success -and `304` if the project is not starred. +Unstars a given project. Returns status code `304` if the project is not starred. ``` DELETE /projects/:id/star @@ -837,10 +832,6 @@ Example response: Archives the project if the user is either admin or the project owner of this project. This action is idempotent, thus archiving an already archived project will not change the project. -Status code 201 with the project as body is given when successful, in case the user doesn't -have the proper access rights, code 403 is returned. Status 404 is returned if the project -doesn't exist, or is hidden to the user. - ``` POST /projects/:id/archive ``` @@ -926,10 +917,6 @@ Example response: Unarchives the project if the user is either admin or the project owner of this project. This action is idempotent, thus unarchiving an non-archived project will not change the project. -Status code 201 with the project as body is given when successful, in case the user doesn't -have the proper access rights, code 403 is returned. Status 404 is returned if the project -doesn't exist, or is hidden to the user. - ``` POST /projects/:id/unarchive ``` diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index efd23d514bc..3fb8b73be6d 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -108,8 +108,7 @@ Example response: ## Delete system hook -Deletes a system hook. It returns `200 OK` if the hooks is deleted and -`404 Not Found` if the hook is not found. +Deletes a system hook. --- diff --git a/doc/api/tags.md b/doc/api/tags.md index 398b080e3f6..14573d48fe4 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -40,9 +40,7 @@ Parameters: ## Get a single repository tag -Get a specific repository tag determined by its name. It returns `200` together -with the tag information if the tag exists. It returns `404` if the tag does not -exist. +Get a specific repository tag determined by its name. ``` GET /projects/:id/repository/tags/:tag_name @@ -124,14 +122,12 @@ Parameters: The message will be `nil` when creating a lightweight tag otherwise it will contain the annotation. -It returns 201 if the operation succeed. In case of an error, -405 with an explaining error message is returned. +In case of an error, +status code `405` with an explaining error message is returned. ## Delete a tag -Deletes a tag of a repository with given name. On success, this API method -returns 200 with the name of the deleted tag. If the tag does not exist, the -API returns 404. +Deletes a tag of a repository with given name. ``` DELETE /projects/:id/repository/tags/:tag_name @@ -150,9 +146,8 @@ Parameters: ## Create a new release -Add release notes to the existing git tag. It returns 201 if the release is -created successfully. If the tag does not exist, 404 is returned. If there -already exists a release for the given tag, 409 is returned. +Add release notes to the existing git tag. If there +already exists a release for the given tag, status code `409` is returned. ``` POST /projects/:id/repository/tags/:tag_name/release @@ -173,9 +168,7 @@ Parameters: ## Update a release -Updates the release notes of a given release. It returns 200 if the release is -successfully updated. If the tag or the release does not exist, it returns 404 -with a proper error message. +Updates the release notes of a given release. ``` PUT /projects/:id/repository/tags/:tag_name/release diff --git a/doc/api/users.md b/doc/api/users.md index b38c335490a..52a6b691610 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -271,8 +271,8 @@ Parameters: - `can_create_group` (optional) - User can create groups - true or false - `external` (optional) - Flags the user as external - true or false(default) -Note, at the moment this method does only return a 404 error, -even in cases where a 409 (Conflict) would be more appropriate, +Note, at the moment this method does only return a `404` error, +even in cases where a `409` (Conflict) would be more appropriate, e.g. when renaming the email address to some existing one. ## User deletion @@ -449,8 +449,6 @@ Parameters: - `title` (required) - new SSH Key's title - `key` (required) - new SSH key -Will return created key with status `201 Created` on success, or `404 Not found` on fail. - ## Delete SSH key for current user Deletes key owned by currently authenticated user. @@ -581,8 +579,6 @@ Parameters: - `id` (required) - id of specified user - `email` (required) - email address -Will return created email with status `201 Created` on success, or `404 Not found` on fail. - ## Delete email for current user Deletes email owned by currently authenticated user. -- cgit v1.2.1 From 60cd47bc8ede7411cc2eea7c1fb72dd290eb679a Mon Sep 17 00:00:00 2001 From: Luis Alonso Chavez Armendariz Date: Fri, 25 Nov 2016 00:29:26 -0700 Subject: Fix bad selection on dropdown menu for tags filter --- app/views/projects/tags/index.html.haml | 7 ++++--- changelogs/unreleased/issue_24958.yml | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/issue_24958.yml diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 7a0d9dcc94f..b43b13de4ca 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -10,15 +10,16 @@ .nav-controls = form_tag(filter_tags_path, method: :get) do = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } + .dropdown.inline %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } %span.light - = @sort.humanize + = projects_sort_options_hash[@sort] = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to filter_tags_path(sort: nil) do - Name + = link_to filter_tags_path(sort: sort_value_name) do + = sort_title_name = link_to filter_tags_path(sort: sort_value_recently_updated) do = sort_title_recently_updated = link_to filter_tags_path(sort: sort_value_oldest_updated) do diff --git a/changelogs/unreleased/issue_24958.yml b/changelogs/unreleased/issue_24958.yml new file mode 100644 index 00000000000..dbbbbf9d28d --- /dev/null +++ b/changelogs/unreleased/issue_24958.yml @@ -0,0 +1,4 @@ +--- +title: Fix bad selection on dropdown menu for tags filter +merge_request: +author: Luis Alonso Chavez Armendariz -- cgit v1.2.1 From fda61998bb18e493936cf1c6dfb368bbba9f9424 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 10:10:55 +0100 Subject: Update pipeline processing specs for creating builds --- spec/services/ci/process_pipeline_service_spec.rb | 63 ++++++++++------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index ff113efd916..f8b7e7488c2 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -327,62 +327,55 @@ describe Ci::ProcessPipelineService, services: true do end end - context 'creates a builds from .gitlab-ci.yml' do - let(:config) do - YAML.dump({ - rspec: { - stage: 'test', - script: 'rspec' - }, - rubocop: { - stage: 'test', - script: 'rubocop' - }, - deploy: { - stage: 'deploy', - script: 'deploy' - } - }) - end - - # Using stubbed .gitlab-ci.yml created in commit factory - # - + context 'when there are builds in multiple stages' do before do - stub_ci_pipeline_yaml_file(config) create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0) create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0) + create(:ci_build, :created, pipeline: pipeline, name: 'rspec', stage: 'test', stage_idx: 1) + create(:ci_build, :created, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 1) + create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 2) end - it 'when processing a pipeline' do - # Currently we have two builds with state created + it 'processes the pipeline' do + # Currently we have five builds with state created + # expect(builds.count).to eq(0) - expect(all_builds.count).to eq(2) + expect(all_builds.count).to eq(5) + + # Process builds will mark the created as pending + # + process_pipeline - # Create builds will mark the created as pending - expect(process_pipeline).to be_truthy expect(builds.count).to eq(2) - expect(all_builds.count).to eq(2) + expect(all_builds.count).to eq(5) - # When we builds succeed we will create a rest of pipeline from .gitlab-ci.yml - # We will have 2 succeeded, 2 pending (from stage test), total 5 (one more build from deploy) + # When builds succeed we will enqueue remaining builds + # We will have 2 succeeded, 2 pending (from stage test), + # total 5 (one more build from deploy) + # succeed_pending - expect(process_pipeline).to be_truthy + process_pipeline + expect(builds.success.count).to eq(2) expect(builds.pending.count).to eq(2) expect(all_builds.count).to eq(5) # When we succeed the 2 pending from stage test, - # We will queue a deploy stage, no new builds will be created + # We will queue a deploy stage. + # succeed_pending - expect(process_pipeline).to be_truthy + process_pipeline + expect(builds.pending.count).to eq(1) expect(builds.success.count).to eq(4) expect(all_builds.count).to eq(5) - # When we succeed last pending build, we will have a total of 5 succeeded builds, no new builds will be created + # When we succeed last pending build, we will have + # a total of 5 succeeded builds + # succeed_pending - expect(process_pipeline).to be_falsey + process_pipeline + expect(builds.success.count).to eq(5) expect(all_builds.count).to eq(5) end -- cgit v1.2.1 From c1cc252bbd9f47ab8c611df37098ad25833402ea Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 10:44:13 +0100 Subject: Move helpers to the end of process pipeline specs --- spec/services/ci/process_pipeline_service_spec.rb | 58 +++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index f8b7e7488c2..544c88d024f 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -10,22 +10,6 @@ describe Ci::ProcessPipelineService, services: true do end describe '#execute' do - def all_builds - pipeline.builds - end - - def builds - all_builds.where.not(status: [:created, :skipped]) - end - - def process_pipeline - described_class.new(pipeline.project, user).execute(pipeline) - end - - def succeed_pending - builds.pending.update_all(status: 'success') - end - context 'start queuing next builds' do before do create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0) @@ -223,10 +207,6 @@ describe Ci::ProcessPipelineService, services: true do pipeline.builds.running_or_pending.each(&:success) expect(manual_actions).to be_many # production and clear cache end - - def manual_actions - pipeline.manual_actions - end end end @@ -282,15 +262,6 @@ describe Ci::ProcessPipelineService, services: true do expect(builds.map(&:status)).to eq(%w[success skipped pending]) end end - - def create_build(name, stage_idx, when_value = nil) - create(:ci_build, - :created, - pipeline: pipeline, - name: name, - stage_idx: stage_idx, - when: when_value) - end end context 'when failed build in the middle stage is retried' do @@ -381,4 +352,33 @@ describe Ci::ProcessPipelineService, services: true do end end end + + def all_builds + pipeline.builds + end + + def builds + all_builds.where.not(status: [:created, :skipped]) + end + + def process_pipeline + described_class.new(pipeline.project, user).execute(pipeline) + end + + def succeed_pending + builds.pending.update_all(status: 'success') + end + + def manual_actions + pipeline.manual_actions + end + + def create_build(name, stage_idx, when_value = nil) + create(:ci_build, + :created, + pipeline: pipeline, + name: name, + stage_idx: stage_idx, + when: when_value) + end end -- cgit v1.2.1 From 5131f8dedf3584597f417b936d3397b54e56c2e1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 10:45:36 +0100 Subject: Remove remaining calls to CI yaml in pipeline specs --- spec/services/ci/process_pipeline_service_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 544c88d024f..b130f876259 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -3,11 +3,6 @@ require 'spec_helper' describe Ci::ProcessPipelineService, services: true do let(:pipeline) { create(:ci_pipeline, ref: 'master') } let(:user) { create(:user) } - let(:config) { nil } - - before do - allow(pipeline).to receive(:ci_yaml_file).and_return(config) - end describe '#execute' do context 'start queuing next builds' do -- cgit v1.2.1 From 94100d4e722ad61fec3e8ab3f9889ffabba1100f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 10:57:03 +0100 Subject: Add Changelog entry for pipeline creation improvements --- .../unreleased/fix-create-pipeline-with-builds-in-transaction.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml diff --git a/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml new file mode 100644 index 00000000000..e37841e80c3 --- /dev/null +++ b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml @@ -0,0 +1,4 @@ +--- +title: Create builds in transaction to avoid empty pipelines +merge_request: 7742 +author: -- cgit v1.2.1 From 0bf14cb0b510cd1c74b0ef94c109d519983fddd0 Mon Sep 17 00:00:00 2001 From: winniehell Date: Fri, 18 Nov 2016 21:31:11 +0100 Subject: Create dynamic fixture for build_spec (!7589) --- .../create-dynamic-fixture-for-build_spec.yml | 4 ++ spec/javascripts/build_spec.js.es6 | 12 +---- spec/javascripts/fixtures/build.html.haml | 62 ---------------------- spec/javascripts/fixtures/builds.rb | 32 +++++++++++ 4 files changed, 38 insertions(+), 72 deletions(-) create mode 100644 changelogs/unreleased/create-dynamic-fixture-for-build_spec.yml delete mode 100644 spec/javascripts/fixtures/build.html.haml create mode 100644 spec/javascripts/fixtures/builds.rb diff --git a/changelogs/unreleased/create-dynamic-fixture-for-build_spec.yml b/changelogs/unreleased/create-dynamic-fixture-for-build_spec.yml new file mode 100644 index 00000000000..f0d9ff0c34f --- /dev/null +++ b/changelogs/unreleased/create-dynamic-fixture-for-build_spec.yml @@ -0,0 +1,4 @@ +--- +title: Create dynamic fixture for build_spec +merge_request: 7589 +author: winniehell diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 index ee192c4f18a..1a56819724d 100644 --- a/spec/javascripts/build_spec.js.es6 +++ b/spec/javascripts/build_spec.js.es6 @@ -11,10 +11,10 @@ (() => { describe('Build', () => { - fixture.preload('build.html'); + fixture.preload('builds/build-with-artifacts.html.raw'); beforeEach(function () { - fixture.load('build.html'); + fixture.load('builds/build-with-artifacts.html.raw'); spyOn($, 'ajax'); }); @@ -28,15 +28,7 @@ }); describe('setup', function () { - const removeDate = new Date(); - removeDate.setUTCFullYear(removeDate.getUTCFullYear() + 1); - // give the test three days to run - removeDate.setTime(removeDate.getTime() + (3 * 24 * 60 * 60 * 1000)); - beforeEach(function () { - const removeDateElement = document.querySelector('.js-artifacts-remove'); - removeDateElement.innerText = removeDate.toString(); - this.build = new Build(); }); diff --git a/spec/javascripts/fixtures/build.html.haml b/spec/javascripts/fixtures/build.html.haml deleted file mode 100644 index 06b49516e5c..00000000000 --- a/spec/javascripts/fixtures/build.html.haml +++ /dev/null @@ -1,62 +0,0 @@ -.build-page - .prepend-top-default - .autoscroll-container - %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll - #js-build-scroll.scroll-controls - %a.btn{href: '#build-trace'} - %i.fa.fa-angle-up - %a.btn{href: '#down-build-trace'} - %i.fa.fa-angle-down - %pre.build-trace#build-trace - %code.bash.js-build-output - %i.fa.fa-refresh.fa-spin.js-build-refresh - -%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar - .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default - Build - %strong #1 - %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" } - %i.fa.fa-angle-double-right - .blocks-container - .dropdown.build-dropdown - .title Stage - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span.stage-selection More - %i.fa.fa-caret-down - %ul.dropdown-menu - %li - %a.stage-item build - %li - %a.stage-item test - %li - %a.stage-item deploy - .builds-container - .build-job{data: {stage: 'build'}} - %a{href: 'http://example.com/root/test-build/builds/1'} - %i.fa.fa-check - %i.fa.fa-check-circle-o - %span - Setup - .build-job{data: {stage: 'test'}} - %a{href: 'http://example.com/root/test-build/builds/2'} - %i.fa.fa-check - %i.fa.fa-check-circle-o - %span - Tests - .build-job{data: {stage: 'deploy'}} - %a{href: 'http://example.com/root/test-build/builds/3'} - %i.fa.fa-check - %i.fa.fa-check-circle-o - %span - Deploy - -.js-build-options{ data: { page_url: 'http://example.com/root/test-build/builds/2', - build_url: 'http://example.com/root/test-build/builds/2.json', - build_status: 'passed', - build_stage: 'test', - log_state: 'buildstate' }} - -%p.build-detail-row - The artifacts will be removed in - %span.js-artifacts-remove - 2016-12-19 09:02:12 UTC diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/builds.rb new file mode 100644 index 00000000000..e47698f71ed --- /dev/null +++ b/spec/javascripts/fixtures/builds.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let(:project) { create(:project_empty_repo) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) } + let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') } + let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') } + + render_views + + before(:all) do + clean_frontend_fixtures('builds/') + end + + before(:each) do + sign_in(admin) + end + + it 'builds/build-with-artifacts.html.raw' do |example| + get :show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: build_with_artifacts.to_param + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end -- cgit v1.2.1 From aae82d766bd3fcbd85bf3e6da9c910b937ac5e72 Mon Sep 17 00:00:00 2001 From: winniehell Date: Sat, 19 Nov 2016 02:06:49 +0100 Subject: Adjust build_spec to match fixture --- spec/javascripts/build_spec.js.es6 | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 index 1a56819724d..d8253c1d0d5 100644 --- a/spec/javascripts/build_spec.js.es6 +++ b/spec/javascripts/build_spec.js.es6 @@ -11,6 +11,13 @@ (() => { describe('Build', () => { + // see spec/factories/ci/builds.rb + const BUILD_TRACE = 'BUILD TRACE'; + // see lib/ci/ansi2html.rb + const INITIAL_BUILD_TRACE_STATE = window.btoa(JSON.stringify({ + offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0, + })); + fixture.preload('builds/build-with-artifacts.html.raw'); beforeEach(function () { @@ -33,11 +40,11 @@ }); it('copies build options', function () { - expect(this.build.pageUrl).toBe('http://example.com/root/test-build/builds/2'); - expect(this.build.buildUrl).toBe('http://example.com/root/test-build/builds/2.json'); - expect(this.build.buildStatus).toBe('passed'); + expect(this.build.pageUrl).toBe('http://test.host/namespace1/project1/builds/1'); + expect(this.build.buildUrl).toBe('http://test.host/namespace1/project1/builds/1.json'); + expect(this.build.buildStatus).toBe('success'); expect(this.build.buildStage).toBe('test'); - expect(this.build.state).toBe('buildstate'); + expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE); }); it('only shows the jobs matching the current stage', function () { @@ -73,7 +80,7 @@ it('displays the initial build trace', function () { expect($.ajax.calls.count()).toBe(1); const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0); - expect(url).toBe('http://example.com/root/test-build/builds/2.json'); + expect(url).toBe('http://test.host/namespace1/project1/builds/1.json'); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); @@ -95,7 +102,7 @@ $('.js-build-options').data('buildStatus', 'running'); this.build = new Build(); spyOn(this.build, 'location') - .and.returnValue('http://example.com/root/test-build/builds/2'); + .and.returnValue('http://test.host/namespace1/project1/builds/1'); }); it('updates the build trace on an interval', function () { @@ -104,7 +111,7 @@ expect($.ajax.calls.count()).toBe(2); let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1); expect(url).toBe( - 'http://example.com/root/test-build/builds/2/trace.json?state=buildstate' + `http://test.host/namespace1/project1/builds/1/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}` ); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); @@ -124,7 +131,7 @@ expect($.ajax.calls.count()).toBe(3); [{ url, dataType, success, context }] = $.ajax.calls.argsFor(2); expect(url).toBe( - 'http://example.com/root/test-build/builds/2/trace.json?state=newstate' + 'http://test.host/namespace1/project1/builds/1/trace.json?state=newstate' ); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); @@ -175,7 +182,7 @@ }); expect(Turbolinks.visit).toHaveBeenCalledWith( - 'http://example.com/root/test-build/builds/2' + 'http://test.host/namespace1/project1/builds/1' ); }); }); -- cgit v1.2.1 From d100f843d784b64b1b73ad8090b855e2ffd985dd Mon Sep 17 00:00:00 2001 From: winniehell Date: Sat, 19 Nov 2016 22:48:49 +0100 Subject: Remove unnecessary IIFE from build_spec --- spec/javascripts/build_spec.js.es6 | 284 ++++++++++++++++++------------------- 1 file changed, 141 insertions(+), 143 deletions(-) diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 index d8253c1d0d5..c9a314d2a82 100644 --- a/spec/javascripts/build_spec.js.es6 +++ b/spec/javascripts/build_spec.js.es6 @@ -9,183 +9,181 @@ //= require jquery.nicescroll //= require turbolinks -(() => { - describe('Build', () => { - // see spec/factories/ci/builds.rb - const BUILD_TRACE = 'BUILD TRACE'; - // see lib/ci/ansi2html.rb - const INITIAL_BUILD_TRACE_STATE = window.btoa(JSON.stringify({ - offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0, - })); - - fixture.preload('builds/build-with-artifacts.html.raw'); +describe('Build', () => { + // see spec/factories/ci/builds.rb + const BUILD_TRACE = 'BUILD TRACE'; + // see lib/ci/ansi2html.rb + const INITIAL_BUILD_TRACE_STATE = window.btoa(JSON.stringify({ + offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0, + })); + + fixture.preload('builds/build-with-artifacts.html.raw'); + + beforeEach(function () { + fixture.load('builds/build-with-artifacts.html.raw'); + spyOn($, 'ajax'); + }); + describe('constructor', () => { beforeEach(function () { - fixture.load('builds/build-with-artifacts.html.raw'); - spyOn($, 'ajax'); + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); }); - describe('constructor', () => { + describe('setup', function () { beforeEach(function () { - jasmine.clock().install(); + this.build = new Build(); }); - afterEach(() => { - jasmine.clock().uninstall(); + it('copies build options', function () { + expect(this.build.pageUrl).toBe('http://test.host/namespace1/project1/builds/1'); + expect(this.build.buildUrl).toBe('http://test.host/namespace1/project1/builds/1.json'); + expect(this.build.buildStatus).toBe('success'); + expect(this.build.buildStage).toBe('test'); + expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE); }); - describe('setup', function () { - beforeEach(function () { - this.build = new Build(); - }); + it('only shows the jobs matching the current stage', function () { + expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false); + expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true); + expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); + }); - it('copies build options', function () { - expect(this.build.pageUrl).toBe('http://test.host/namespace1/project1/builds/1'); - expect(this.build.buildUrl).toBe('http://test.host/namespace1/project1/builds/1.json'); - expect(this.build.buildStatus).toBe('success'); - expect(this.build.buildStage).toBe('test'); - expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE); - }); + it('selects the current stage in the build dropdown menu', function () { + expect($('.stage-selection').text()).toBe('test'); + }); - it('only shows the jobs matching the current stage', function () { - expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false); - expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true); - expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); - }); + it('updates the jobs when the build dropdown changes', function () { + $('.stage-item:contains("build")').click(); - it('selects the current stage in the build dropdown menu', function () { - expect($('.stage-selection').text()).toBe('test'); - }); + expect($('.stage-selection').text()).toBe('build'); + expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true); + expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false); + expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); + }); - it('updates the jobs when the build dropdown changes', function () { - $('.stage-item:contains("build")').click(); + it('displays the remove date correctly', function () { + const removeDateElement = document.querySelector('.js-artifacts-remove'); + expect(removeDateElement.innerText.trim()).toBe('1 year'); + }); + }); - expect($('.stage-selection').text()).toBe('build'); - expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true); - expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false); - expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); - }); + describe('initial build trace', function () { + beforeEach(function () { + new Build(); + }); - it('displays the remove date correctly', function () { - const removeDateElement = document.querySelector('.js-artifacts-remove'); - expect(removeDateElement.innerText.trim()).toBe('1 year'); - }); + it('displays the initial build trace', function () { + expect($.ajax.calls.count()).toBe(1); + const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0); + expect(url).toBe('http://test.host/namespace1/project1/builds/1.json'); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { trace_html: 'Example', status: 'running' }); + + expect($('#build-trace .js-build-output').text()).toMatch(/Example/); }); - describe('initial build trace', function () { - beforeEach(function () { - new Build(); - }); + it('removes the spinner', function () { + const [{ success, context }] = $.ajax.calls.argsFor(0); + success.call(context, { trace_html: 'Example', status: 'success' }); - it('displays the initial build trace', function () { - expect($.ajax.calls.count()).toBe(1); - const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0); - expect(url).toBe('http://test.host/namespace1/project1/builds/1.json'); - expect(dataType).toBe('json'); - expect(success).toEqual(jasmine.any(Function)); + expect($('.js-build-refresh').length).toBe(0); + }); + }); - success.call(context, { trace_html: 'Example', status: 'running' }); + describe('running build', function () { + beforeEach(function () { + $('.js-build-options').data('buildStatus', 'running'); + this.build = new Build(); + spyOn(this.build, 'location') + .and.returnValue('http://test.host/namespace1/project1/builds/1'); + }); - expect($('#build-trace .js-build-output').text()).toMatch(/Example/); + it('updates the build trace on an interval', function () { + jasmine.clock().tick(4001); + + expect($.ajax.calls.count()).toBe(2); + let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1); + expect(url).toBe( + `http://test.host/namespace1/project1/builds/1/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}` + ); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { + html: 'Update', + status: 'running', + state: 'newstate', + append: true, }); - it('removes the spinner', function () { - const [{ success, context }] = $.ajax.calls.argsFor(0); - success.call(context, { trace_html: 'Example', status: 'success' }); + expect($('#build-trace .js-build-output').text()).toMatch(/Update/); + expect(this.build.state).toBe('newstate'); + + jasmine.clock().tick(4001); - expect($('.js-build-refresh').length).toBe(0); + expect($.ajax.calls.count()).toBe(3); + [{ url, dataType, success, context }] = $.ajax.calls.argsFor(2); + expect(url).toBe( + 'http://test.host/namespace1/project1/builds/1/trace.json?state=newstate' + ); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { + html: 'More', + status: 'running', + state: 'finalstate', + append: true, }); + + expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/); + expect(this.build.state).toBe('finalstate'); }); - describe('running build', function () { - beforeEach(function () { - $('.js-build-options').data('buildStatus', 'running'); - this.build = new Build(); - spyOn(this.build, 'location') - .and.returnValue('http://test.host/namespace1/project1/builds/1'); + it('replaces the entire build trace', function () { + jasmine.clock().tick(4001); + let [{ success, context }] = $.ajax.calls.argsFor(1); + success.call(context, { + html: 'Update', + status: 'running', + append: true, }); - it('updates the build trace on an interval', function () { - jasmine.clock().tick(4001); - - expect($.ajax.calls.count()).toBe(2); - let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1); - expect(url).toBe( - `http://test.host/namespace1/project1/builds/1/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}` - ); - expect(dataType).toBe('json'); - expect(success).toEqual(jasmine.any(Function)); - - success.call(context, { - html: 'Update', - status: 'running', - state: 'newstate', - append: true, - }); - - expect($('#build-trace .js-build-output').text()).toMatch(/Update/); - expect(this.build.state).toBe('newstate'); - - jasmine.clock().tick(4001); - - expect($.ajax.calls.count()).toBe(3); - [{ url, dataType, success, context }] = $.ajax.calls.argsFor(2); - expect(url).toBe( - 'http://test.host/namespace1/project1/builds/1/trace.json?state=newstate' - ); - expect(dataType).toBe('json'); - expect(success).toEqual(jasmine.any(Function)); - - success.call(context, { - html: 'More', - status: 'running', - state: 'finalstate', - append: true, - }); - - expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/); - expect(this.build.state).toBe('finalstate'); - }); + expect($('#build-trace .js-build-output').text()).toMatch(/Update/); - it('replaces the entire build trace', function () { - jasmine.clock().tick(4001); - let [{ success, context }] = $.ajax.calls.argsFor(1); - success.call(context, { - html: 'Update', - status: 'running', - append: true, - }); - - expect($('#build-trace .js-build-output').text()).toMatch(/Update/); - - jasmine.clock().tick(4001); - [{ success, context }] = $.ajax.calls.argsFor(2); - success.call(context, { - html: 'Different', - status: 'running', - append: false, - }); - - expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/); - expect($('#build-trace .js-build-output').text()).toMatch(/Different/); + jasmine.clock().tick(4001); + [{ success, context }] = $.ajax.calls.argsFor(2); + success.call(context, { + html: 'Different', + status: 'running', + append: false, }); - it('reloads the page when the build is done', function () { - spyOn(Turbolinks, 'visit'); + expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/); + expect($('#build-trace .js-build-output').text()).toMatch(/Different/); + }); - jasmine.clock().tick(4001); - const [{ success, context }] = $.ajax.calls.argsFor(1); - success.call(context, { - html: 'Final', - status: 'passed', - append: true, - }); + it('reloads the page when the build is done', function () { + spyOn(Turbolinks, 'visit'); - expect(Turbolinks.visit).toHaveBeenCalledWith( - 'http://test.host/namespace1/project1/builds/1' - ); + jasmine.clock().tick(4001); + const [{ success, context }] = $.ajax.calls.argsFor(1); + success.call(context, { + html: 'Final', + status: 'passed', + append: true, }); + + expect(Turbolinks.visit).toHaveBeenCalledWith( + 'http://test.host/namespace1/project1/builds/1' + ); }); }); }); -})(); +}); -- cgit v1.2.1 From 918bc207c680bed46e82bef996aea027ac72b38d Mon Sep 17 00:00:00 2001 From: winniehell Date: Sat, 19 Nov 2016 22:59:32 +0100 Subject: Use Rails test host name for frontend fixtures --- spec/javascripts/build_spec.js.es6 | 20 ++++++++------------ spec/javascripts/issue_spec.js | 2 +- spec/javascripts/spec_helper.js | 5 +++++ spec/support/javascript_fixtures_helpers.rb | 4 +++- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 index c9a314d2a82..50411695925 100644 --- a/spec/javascripts/build_spec.js.es6 +++ b/spec/javascripts/build_spec.js.es6 @@ -10,6 +10,7 @@ //= require turbolinks describe('Build', () => { + const BUILD_URL = `${gl.TEST_HOST}/namespace1/project1/builds/1`; // see spec/factories/ci/builds.rb const BUILD_TRACE = 'BUILD TRACE'; // see lib/ci/ansi2html.rb @@ -39,8 +40,8 @@ describe('Build', () => { }); it('copies build options', function () { - expect(this.build.pageUrl).toBe('http://test.host/namespace1/project1/builds/1'); - expect(this.build.buildUrl).toBe('http://test.host/namespace1/project1/builds/1.json'); + expect(this.build.pageUrl).toBe(BUILD_URL); + expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`); expect(this.build.buildStatus).toBe('success'); expect(this.build.buildStage).toBe('test'); expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE); @@ -79,7 +80,7 @@ describe('Build', () => { it('displays the initial build trace', function () { expect($.ajax.calls.count()).toBe(1); const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0); - expect(url).toBe('http://test.host/namespace1/project1/builds/1.json'); + expect(url).toBe(`${BUILD_URL}.json`); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); @@ -100,8 +101,7 @@ describe('Build', () => { beforeEach(function () { $('.js-build-options').data('buildStatus', 'running'); this.build = new Build(); - spyOn(this.build, 'location') - .and.returnValue('http://test.host/namespace1/project1/builds/1'); + spyOn(this.build, 'location').and.returnValue(BUILD_URL); }); it('updates the build trace on an interval', function () { @@ -110,7 +110,7 @@ describe('Build', () => { expect($.ajax.calls.count()).toBe(2); let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1); expect(url).toBe( - `http://test.host/namespace1/project1/builds/1/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}` + `${BUILD_URL}/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}` ); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); @@ -129,9 +129,7 @@ describe('Build', () => { expect($.ajax.calls.count()).toBe(3); [{ url, dataType, success, context }] = $.ajax.calls.argsFor(2); - expect(url).toBe( - 'http://test.host/namespace1/project1/builds/1/trace.json?state=newstate' - ); + expect(url).toBe(`${BUILD_URL}/trace.json?state=newstate`); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); @@ -180,9 +178,7 @@ describe('Build', () => { append: true, }); - expect(Turbolinks.visit).toHaveBeenCalledWith( - 'http://test.host/namespace1/project1/builds/1' - ); + expect(Turbolinks.visit).toHaveBeenCalledWith(BUILD_URL); }); }); }); diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index beef46122ab..ab92bdf01fd 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -74,7 +74,7 @@ it('submits an ajax request on tasklist:changed', function() { spyOn(jQuery, 'ajax').and.callFake(function(req) { expect(req.type).toBe('PATCH'); - expect(req.url).toBe('https://fixture.invalid/namespace3/project3/issues/1.json'); + expect(req.url).toBe(gl.TEST_HOST + '/namespace3/project3/issues/1.json'); expect(req.data.issue.description).not.toBe(null); }); diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js index 8a64de4dd43..831dfada952 100644 --- a/spec/javascripts/spec_helper.js +++ b/spec/javascripts/spec_helper.js @@ -41,3 +41,8 @@ }).call(this); + +// defined in ActionDispatch::TestRequest +// see https://github.com/rails/rails/blob/v4.2.7.1/actionpack/lib/action_dispatch/testing/test_request.rb#L7 +window.gl = window.gl || {}; +gl.TEST_HOST = 'http://test.host'; diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb index adc3f48b434..7e066aa115d 100644 --- a/spec/support/javascript_fixtures_helpers.rb +++ b/spec/support/javascript_fixtures_helpers.rb @@ -1,3 +1,4 @@ +require 'action_dispatch/testing/test_request' require 'fileutils' require 'gitlab/popen' @@ -36,7 +37,8 @@ module JavaScriptFixturesHelpers fixture = doc.to_html # replace relative links - fixture.gsub!(%r{="/}, '="https://fixture.invalid/') + test_host = ActionDispatch::TestRequest::DEFAULT_ENV['HTTP_HOST'] + fixture.gsub!(%r{="/}, "=\"http://#{test_host}/") end FileUtils.mkdir_p(File.dirname(fixture_file_name)) -- cgit v1.2.1 From 82429b6978361daaf703a3438c7822ac956a3aa4 Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 20 Nov 2016 00:40:37 +0100 Subject: Explicitly name namespace and projects for frontend fixtures --- spec/javascripts/build_spec.js.es6 | 2 +- spec/javascripts/fixtures/builds.rb | 3 ++- spec/javascripts/fixtures/issues.rb | 3 ++- spec/javascripts/issue_spec.js | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 index 50411695925..304c4d4e29d 100644 --- a/spec/javascripts/build_spec.js.es6 +++ b/spec/javascripts/build_spec.js.es6 @@ -10,7 +10,7 @@ //= require turbolinks describe('Build', () => { - const BUILD_URL = `${gl.TEST_HOST}/namespace1/project1/builds/1`; + const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`; // see spec/factories/ci/builds.rb const BUILD_TRACE = 'BUILD TRACE'; // see lib/ci/ansi2html.rb diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/builds.rb index e47698f71ed..978e25a1c32 100644 --- a/spec/javascripts/fixtures/builds.rb +++ b/spec/javascripts/fixtures/builds.rb @@ -4,7 +4,8 @@ describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller include JavaScriptFixturesHelpers let(:admin) { create(:admin) } - let(:project) { create(:project_empty_repo) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project_empty_repo, namespace: namespace, path: 'builds-project') } let(:pipeline) { create(:ci_empty_pipeline, project: project) } let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) } let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') } diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index d95eb851421..c10784fe5ae 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -4,7 +4,8 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller include JavaScriptFixturesHelpers let(:admin) { create(:admin) } - let(:project) { create(:project_empty_repo) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') } render_views diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index ab92bdf01fd..14af6644de1 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -74,7 +74,7 @@ it('submits an ajax request on tasklist:changed', function() { spyOn(jQuery, 'ajax').and.callFake(function(req) { expect(req.type).toBe('PATCH'); - expect(req.url).toBe(gl.TEST_HOST + '/namespace3/project3/issues/1.json'); + expect(req.url).toBe(gl.TEST_HOST + '/frontend-fixtures/issues-project/issues/1.json'); // eslint-disable-line prefer-template expect(req.data.issue.description).not.toBe(null); }); -- cgit v1.2.1 From d9fe5c259d8880fcb9f8c1cc3322836d4d35ef93 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 23 Nov 2016 21:36:34 +0100 Subject: Strip tags from fixtures to ignore CSS --- spec/support/javascript_fixtures_helpers.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb index 7e066aa115d..99e98eebdb4 100644 --- a/spec/support/javascript_fixtures_helpers.rb +++ b/spec/support/javascript_fixtures_helpers.rb @@ -31,6 +31,9 @@ module JavaScriptFixturesHelpers if response_mime_type.html? doc = Nokogiri::HTML::DocumentFragment.parse(fixture) + link_tags = doc.css('link') + link_tags.remove + scripts = doc.css('script') scripts.remove -- cgit v1.2.1 From 31a5ed97a74e250887721413f5a956a93dc2e1b1 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 23 Nov 2016 21:45:39 +0100 Subject: Prefer arrow functions in build_spec.js.es6 --- spec/javascripts/build_spec.js.es6 | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 index 304c4d4e29d..d92cc433670 100644 --- a/spec/javascripts/build_spec.js.es6 +++ b/spec/javascripts/build_spec.js.es6 @@ -20,13 +20,13 @@ describe('Build', () => { fixture.preload('builds/build-with-artifacts.html.raw'); - beforeEach(function () { + beforeEach(() => { fixture.load('builds/build-with-artifacts.html.raw'); spyOn($, 'ajax'); }); describe('constructor', () => { - beforeEach(function () { + beforeEach(() => { jasmine.clock().install(); }); @@ -34,7 +34,7 @@ describe('Build', () => { jasmine.clock().uninstall(); }); - describe('setup', function () { + describe('setup', () => { beforeEach(function () { this.build = new Build(); }); @@ -47,17 +47,17 @@ describe('Build', () => { expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE); }); - it('only shows the jobs matching the current stage', function () { + it('only shows the jobs matching the current stage', () => { expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false); expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true); expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); }); - it('selects the current stage in the build dropdown menu', function () { + it('selects the current stage in the build dropdown menu', () => { expect($('.stage-selection').text()).toBe('test'); }); - it('updates the jobs when the build dropdown changes', function () { + it('updates the jobs when the build dropdown changes', () => { $('.stage-item:contains("build")').click(); expect($('.stage-selection').text()).toBe('build'); @@ -66,18 +66,18 @@ describe('Build', () => { expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); }); - it('displays the remove date correctly', function () { + it('displays the remove date correctly', () => { const removeDateElement = document.querySelector('.js-artifacts-remove'); expect(removeDateElement.innerText.trim()).toBe('1 year'); }); }); - describe('initial build trace', function () { - beforeEach(function () { + describe('initial build trace', () => { + beforeEach(() => { new Build(); }); - it('displays the initial build trace', function () { + it('displays the initial build trace', () => { expect($.ajax.calls.count()).toBe(1); const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0); expect(url).toBe(`${BUILD_URL}.json`); @@ -89,7 +89,7 @@ describe('Build', () => { expect($('#build-trace .js-build-output').text()).toMatch(/Example/); }); - it('removes the spinner', function () { + it('removes the spinner', () => { const [{ success, context }] = $.ajax.calls.argsFor(0); success.call(context, { trace_html: 'Example', status: 'success' }); @@ -97,7 +97,7 @@ describe('Build', () => { }); }); - describe('running build', function () { + describe('running build', () => { beforeEach(function () { $('.js-build-options').data('buildStatus', 'running'); this.build = new Build(); @@ -144,7 +144,7 @@ describe('Build', () => { expect(this.build.state).toBe('finalstate'); }); - it('replaces the entire build trace', function () { + it('replaces the entire build trace', () => { jasmine.clock().tick(4001); let [{ success, context }] = $.ajax.calls.argsFor(1); success.call(context, { @@ -167,7 +167,7 @@ describe('Build', () => { expect($('#build-trace .js-build-output').text()).toMatch(/Different/); }); - it('reloads the page when the build is done', function () { + it('reloads the page when the build is done', () => { spyOn(Turbolinks, 'visit'); jasmine.clock().tick(4001); -- cgit v1.2.1 From 9c49fa2d92a3ab1051df87e4d75d9802a1b7cfc7 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 24 Nov 2016 12:38:54 +0100 Subject: fix for builds with no start date and spec --- app/serializers/entity_date_helper.rb | 2 ++ spec/serializers/analytics_build_entity_spec.rb | 31 ++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index 918abba8d99..3cc98fb18a1 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -2,6 +2,8 @@ module EntityDateHelper include ActionView::Helpers::DateHelper def interval_in_words(diff) + return 'not started' unless diff + "#{distance_of_time_in_words(Time.now, diff)} ago" end diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb index c0b7e86b17c..ba562353661 100644 --- a/spec/serializers/analytics_build_entity_spec.rb +++ b/spec/serializers/analytics_build_entity_spec.rb @@ -7,7 +7,9 @@ describe AnalyticsBuildEntity do context 'build with an author' do let(:user) { create(:user) } - let(:build) { create(:ci_build, author: user, started_at: 2.hours.ago, finished_at: 1.hour.ago) } + let(:started_at) { 2.hours.ago } + let(:finished_at) { 1.hour.ago } + let(:build) { create(:ci_build, author: user, started_at: started_at, finished_at: finished_at) } subject { entity.as_json } @@ -31,5 +33,32 @@ describe AnalyticsBuildEntity do it 'contains the duration' do expect(subject[:total_time]).to eq(hours: 1 ) end + + context 'no started at or finished at date' do + let(:started_at) { nil } + let(:finished_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + + it '' + end + + context 'no started at date' do + let(:started_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + end + + context 'no finished at date' do + let(:finished_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + end end end -- cgit v1.2.1 From aa895a64d928f8292ff9df526831ae74d250f748 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 24 Nov 2016 12:42:25 +0100 Subject: Add changelog entry --- changelogs/unreleased/fix-ca-no-date.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/fix-ca-no-date.yml diff --git a/changelogs/unreleased/fix-ca-no-date.yml b/changelogs/unreleased/fix-ca-no-date.yml new file mode 100644 index 00000000000..6de4a56ac0d --- /dev/null +++ b/changelogs/unreleased/fix-ca-no-date.yml @@ -0,0 +1,4 @@ +--- +title: Fix for error thrown in cycle analytics events if build has not started +merge_request: +author: -- cgit v1.2.1 From 830f739b99b36f8862dadc524e4aa72ec5a3366e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 25 Nov 2016 11:22:44 +0100 Subject: use an empty total time when the build has not started yet so the UI knows --- app/serializers/analytics_build_entity.rb | 6 +++++- app/serializers/entity_date_helper.rb | 2 +- spec/serializers/analytics_build_entity_spec.rb | 24 +++++++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index abefcd5cc02..206a7eadbcf 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity end expose :duration, as: :total_time do |build| - distance_of_time_as_hash(build.duration.to_f) + build_started?(build) ? distance_of_time_as_hash(build.duration.to_f) : {} end expose :branch do @@ -37,4 +37,8 @@ class AnalyticsBuildEntity < Grape::Entity def url_to(route, build, id = nil) public_send("#{route}_url", build.project.namespace, build.project, id || build) end + + def build_started?(build) + build.duration && build[:started_at] + end end diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index 3cc98fb18a1..9607ad55a8b 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -2,7 +2,7 @@ module EntityDateHelper include ActionView::Helpers::DateHelper def interval_in_words(diff) - return 'not started' unless diff + return 'Not started' unless diff "#{distance_of_time_in_words(Time.now, diff)} ago" end diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb index ba562353661..1a9ad1968bd 100644 --- a/spec/serializers/analytics_build_entity_spec.rb +++ b/spec/serializers/analytics_build_entity_spec.rb @@ -42,7 +42,13 @@ describe AnalyticsBuildEntity do expect{ subject[:date] }.not_to raise_error end - it '' + it 'shows the right message' do + expect(subject[:date]).to eq('Not started') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({}) + end end context 'no started at date' do @@ -51,6 +57,14 @@ describe AnalyticsBuildEntity do it 'does not blow up' do expect{ subject[:date] }.not_to raise_error end + + it 'shows the right message' do + expect(subject[:date]).to eq('Not started') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({}) + end end context 'no finished at date' do @@ -59,6 +73,14 @@ describe AnalyticsBuildEntity do it 'does not blow up' do expect{ subject[:date] }.not_to raise_error end + + it 'shows the right message' do + expect(subject[:date]).to eq('about 2 hours ago') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({hours: 2}) + end end end end -- cgit v1.2.1 From 94a74e79cb087cc499add60ab638d999bc7e3815 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 25 Nov 2016 11:46:13 +0100 Subject: fix rubocop warning --- spec/serializers/analytics_build_entity_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb index 1a9ad1968bd..6b33fe66a63 100644 --- a/spec/serializers/analytics_build_entity_spec.rb +++ b/spec/serializers/analytics_build_entity_spec.rb @@ -79,7 +79,7 @@ describe AnalyticsBuildEntity do end it 'shows the right total time' do - expect(subject[:total_time]).to eq({hours: 2}) + expect(subject[:total_time]).to eq({ hours: 2 }) end end end -- cgit v1.2.1 From b8e10e327fa2b51a7f645f10576b26dca0b8341e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 11:59:12 +0100 Subject: Fix tests for allowing merged if pipeline succeeded --- .../merge_requests/only_allow_merge_if_build_succeeds_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 1ec3103feef..7e2907cd26f 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -38,7 +38,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: it 'does not allow to merge immediately' do visit_merge_request(merge_request) - expect(page).to have_button 'Merge When Build Succeeds' + expect(page).to have_button 'Merge When Pipeline Succeeds' expect(page).not_to have_button 'Select Merge Moment' end end @@ -97,7 +97,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature: it 'allows MR to be merged immediately', js: true do visit_merge_request(merge_request) - expect(page).to have_button 'Merge When Build Succeeds' + expect(page).to have_button 'Merge When Pipeline Succeeds' click_button 'Select Merge Moment' expect(page).to have_content 'Merge Immediately' -- cgit v1.2.1 From d71ad49fc570ef617d0bbf99af53596ef5d48892 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 21 Nov 2016 22:27:10 +0100 Subject: Accept a valid ref for issue show For example, now we support `/gitlab issue show #1`. Where the # used to trip the regex. --- lib/gitlab/chat_commands/issue_show.rb | 2 +- spec/lib/gitlab/chat_commands/issue_show_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb index f5bceb038e5..2a45d49cf6b 100644 --- a/lib/gitlab/chat_commands/issue_show.rb +++ b/lib/gitlab/chat_commands/issue_show.rb @@ -2,7 +2,7 @@ module Gitlab module ChatCommands class IssueShow < IssueCommand def self.match(text) - /\Aissue\s+show\s+(?\d+)/.match(text) + /\Aissue\s+show\s+#{Issue.reference_prefix}?(?\d+)/.match(text) end def self.help_message diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb index 331a4604e9b..2eab73e49e5 100644 --- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb @@ -19,6 +19,14 @@ describe Gitlab::ChatCommands::IssueShow, service: true do it 'returns the issue' do expect(subject.iid).to be issue.iid end + + context 'when its reference is given' do + let(:regex_match) { described_class.match("issue show #{issue.to_reference}") } + + it 'shows the issue' do + expect(subject.iid).to be issue.iid + end + end end context 'the issue does not exist' do -- cgit v1.2.1 From 92b2c74ce14238c1032bd9faac6d178d25433532 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 24 Nov 2016 10:40:44 +0100 Subject: Refresh project authorizations using a Redis lease When I proposed using serializable transactions I was hoping we would be able to refresh data of individual users concurrently. Unfortunately upon closer inspection it was revealed this was not the case. This could result in a lot of queries failing due to serialization errors, overloading the database in the process (given enough workers trying to update the target table). To work around this we're now using a Redis lease that is cancelled upon completion. This ensures we can update the data of different users concurrently without overloading the database. The code will try to obtain the lease until it succeeds, waiting at least 1 second between retries. This is necessary as we may otherwise end up _not_ updating the data which is not an option. --- app/models/user.rb | 36 +++++++++------------- app/workers/authorized_projects_worker.rb | 23 ++++++++++++-- .../refresh-authorizations-with-lease.yml | 4 +++ db/fixtures/development/04_project.rb | 1 - db/fixtures/development/06_teams.rb | 1 - db/fixtures/development/17_cycle_analytics.rb | 1 - db/fixtures/support/serialized_transaction.rb | 9 ------ lib/gitlab/database.rb | 7 ----- spec/models/user_spec.rb | 27 ++++++++++++++++ spec/workers/authorized_projects_worker_spec.rb | 23 ++++++++++---- 10 files changed, 84 insertions(+), 48 deletions(-) create mode 100644 changelogs/unreleased/refresh-authorizations-with-lease.yml delete mode 100644 db/fixtures/support/serialized_transaction.rb diff --git a/app/models/user.rb b/app/models/user.rb index 513a19d81d2..ad4b4d9381b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -445,27 +445,21 @@ class User < ActiveRecord::Base end def refresh_authorized_projects - loop do - begin - Gitlab::Database.serialized_transaction do - project_authorizations.delete_all - - # project_authorizations_union can return multiple records for the same project/user with - # different access_level so we take row with the maximum access_level - project_authorizations.connection.execute <<-SQL - INSERT INTO project_authorizations (user_id, project_id, access_level) - SELECT user_id, project_id, MAX(access_level) AS access_level - FROM (#{project_authorizations_union.to_sql}) sub - GROUP BY user_id, project_id - SQL - - update_column(:authorized_projects_populated, true) unless authorized_projects_populated - end - - break - # In the event of a concurrent modification Rails raises StatementInvalid. - # In this case we want to keep retrying until the transaction succeeds - rescue ActiveRecord::StatementInvalid + transaction do + project_authorizations.delete_all + + # project_authorizations_union can return multiple records for the same + # project/user with different access_level so we take row with the maximum + # access_level + project_authorizations.connection.execute <<-SQL + INSERT INTO project_authorizations (user_id, project_id, access_level) + SELECT user_id, project_id, MAX(access_level) AS access_level + FROM (#{project_authorizations_union.to_sql}) sub + GROUP BY user_id, project_id + SQL + + unless authorized_projects_populated + update_column(:authorized_projects_populated, true) end end end diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb index 331727ba9d8..fccddb70d18 100644 --- a/app/workers/authorized_projects_worker.rb +++ b/app/workers/authorized_projects_worker.rb @@ -2,14 +2,33 @@ class AuthorizedProjectsWorker include Sidekiq::Worker include DedicatedSidekiqQueue + LEASE_TIMEOUT = 1.minute.to_i + def self.bulk_perform_async(args_list) Sidekiq::Client.push_bulk('class' => self, 'args' => args_list) end def perform(user_id) user = User.find_by(id: user_id) - return unless user - user.refresh_authorized_projects + refresh(user) if user + end + + def refresh(user) + lease_key = "refresh_authorized_projects:#{user.id}" + lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) + + until uuid = lease.try_obtain + # Keep trying until we obtain the lease. If we don't do so we may end up + # not updating the list of authorized projects properly. To prevent + # hammering Redis too much we'll wait for a bit between retries. + sleep(1) + end + + begin + user.refresh_authorized_projects + ensure + Gitlab::ExclusiveLease.cancel(lease_key, uuid) + end end end diff --git a/changelogs/unreleased/refresh-authorizations-with-lease.yml b/changelogs/unreleased/refresh-authorizations-with-lease.yml new file mode 100644 index 00000000000..bb9b77018e3 --- /dev/null +++ b/changelogs/unreleased/refresh-authorizations-with-lease.yml @@ -0,0 +1,4 @@ +--- +title: Use a Redis lease for updating authorized projects +merge_request: 7733 +author: diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index 18a2df7c059..a984eda5ab5 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -1,5 +1,4 @@ require 'sidekiq/testing' -require './db/fixtures/support/serialized_transaction' Sidekiq::Testing.inline! do Gitlab::Seeder.quiet do diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb index 04c3690e152..5c2a03fec3f 100644 --- a/db/fixtures/development/06_teams.rb +++ b/db/fixtures/development/06_teams.rb @@ -1,5 +1,4 @@ require 'sidekiq/testing' -require './db/fixtures/support/serialized_transaction' Sidekiq::Testing.inline! do Gitlab::Seeder.quiet do diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index 7b3908fae98..916ee8dbac8 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -1,6 +1,5 @@ require 'sidekiq/testing' require './spec/support/test_env' -require './db/fixtures/support/serialized_transaction' class Gitlab::Seeder::CycleAnalytics def initialize(project, perf: false) diff --git a/db/fixtures/support/serialized_transaction.rb b/db/fixtures/support/serialized_transaction.rb deleted file mode 100644 index d3305b661e5..00000000000 --- a/db/fixtures/support/serialized_transaction.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'gitlab/database' - -module Gitlab - module Database - def self.serialized_transaction - connection.transaction { yield } - end - end -end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 2d5c9232425..55b8f888d53 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -35,13 +35,6 @@ module Gitlab order end - def self.serialized_transaction - opts = {} - opts[:isolation] = :serializable unless Rails.env.test? && connection.transaction_open? - - connection.transaction(opts) { yield } - end - def self.random Gitlab::Database.postgresql? ? "RANDOM()" : "RAND()" end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 91826e5884d..14c891994d0 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1349,4 +1349,31 @@ describe User, models: true do expect(projects).to be_empty end end + + describe '#refresh_authorized_projects', redis: true do + let(:project1) { create(:empty_project) } + let(:project2) { create(:empty_project) } + let(:user) { create(:user) } + + before do + project1.team << [user, :reporter] + project2.team << [user, :guest] + + user.project_authorizations.delete_all + user.refresh_authorized_projects + end + + it 'refreshes the list of authorized projects' do + expect(user.project_authorizations.count).to eq(2) + end + + it 'sets the authorized_projects_populated column' do + expect(user.authorized_projects_populated).to eq(true) + end + + it 'stores the correct access levels' do + expect(user.project_authorizations.where(access_level: Gitlab::Access::GUEST).exists?).to eq(true) + expect(user.project_authorizations.where(access_level: Gitlab::Access::REPORTER).exists?).to eq(true) + end + end end diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb index 18a1aab766c..95e2458da35 100644 --- a/spec/workers/authorized_projects_worker_spec.rb +++ b/spec/workers/authorized_projects_worker_spec.rb @@ -1,22 +1,33 @@ require 'spec_helper' describe AuthorizedProjectsWorker do + let(:worker) { described_class.new } + describe '#perform' do it "refreshes user's authorized projects" do user = create(:user) - expect(User).to receive(:find_by).with(id: user.id).and_return(user) - expect(user).to receive(:refresh_authorized_projects) + expect(worker).to receive(:refresh).with(an_instance_of(User)) - described_class.new.perform(user.id) + worker.perform(user.id) end - context "when user is not found" do + context "when the user is not found" do it "does nothing" do - expect_any_instance_of(User).not_to receive(:refresh_authorized_projects) + expect(worker).not_to receive(:refresh) - described_class.new.perform(999_999) + described_class.new.perform(-1) end end end + + describe '#refresh', redis: true do + it 'refreshes the authorized projects of the user' do + user = create(:user) + + expect(user).to receive(:refresh_authorized_projects) + + worker.refresh(user) + end + end end -- cgit v1.2.1 From 0ba03d7eb1d80e019b9b8266f0e14356d32e7d69 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 24 Nov 2016 11:25:23 +0100 Subject: Removed data-user-is view code With events no longer being cached this is no longer needed. --- app/models/event.rb | 4 ++++ app/views/events/event/_push.html.haml | 6 +++--- app/views/layouts/_head.html.haml | 2 -- app/views/layouts/_user_styles.html.haml | 24 ------------------------ spec/models/event_spec.rb | 18 ++++++++++++++++++ spec/views/layouts/_head.html.haml_spec.rb | 4 ---- 6 files changed, 25 insertions(+), 33 deletions(-) delete mode 100644 app/views/layouts/_user_styles.html.haml diff --git a/app/models/event.rb b/app/models/event.rb index 216dba46e74..2662f170765 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -347,6 +347,10 @@ class Event < ActiveRecord::Base update_all(last_activity_at: created_at) end + def authored_by?(user) + user ? author_id == user.id : false + end + private def recent_update? diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 44fff49d99c..64ca3c32e01 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -18,7 +18,7 @@ - few_commits.each do |commit| = render "events/commit", commit: commit, project: project, event: event - - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) + - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user) - if event.commits_count > 1 %li.commits-stat - if event.commits_count > 2 @@ -35,12 +35,12 @@ Compare #{from_label}...#{truncate_sha(event.commit_to)} - if create_mr - %span{"data-user-is" => event.author_id, "data-display" => "inline"} + %span or = link_to create_mr_path(project.default_branch, event.ref_name, project) do create a merge request - elsif create_mr - %li.commits-stat{"data-user-is" => event.author_id} + %li.commits-stat = link_to create_mr_path(project.default_branch, event.ref_name, project) do Create Merge Request - elsif event.rm_ref? diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 757de92d6d4..3e488cf73b9 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -56,5 +56,3 @@ = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') = render 'layouts/bootlint' if Rails.env.development? - - = render 'layouts/user_styles' diff --git a/app/views/layouts/_user_styles.html.haml b/app/views/layouts/_user_styles.html.haml deleted file mode 100644 index b76b3cb5510..00000000000 --- a/app/views/layouts/_user_styles.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -:css - [data-user-is] { - display: none !important; - } - - [data-user-is="#{current_user.try(:id)}"] { - display: block !important; - } - - [data-user-is="#{current_user.try(:id)}"][data-display="inline"] { - display: inline !important; - } - - [data-user-is-not] { - display: block !important; - } - - [data-user-is-not][data-display="inline"] { - display: inline !important; - } - - [data-user-is-not="#{current_user.try(:id)}"] { - display: none !important; - } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index b684053cd02..f8660da031d 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -260,6 +260,24 @@ describe Event, models: true do end end + describe '#authored_by?' do + let(:event) { build(:event) } + + it 'returns true when the event author and user are the same' do + expect(event.authored_by?(event.author)).to eq(true) + end + + it 'returns false when passing nil as an argument' do + expect(event.authored_by?(nil)).to eq(false) + end + + it 'returns false when the given user is not the author of the event' do + user = double(:user, id: -1) + + expect(event.authored_by?(user)).to eq(false) + end + end + def create_event(project, user, attrs = {}) data = { before: Gitlab::Git::BLANK_SHA, diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb index 3fddfb3b62f..8020faa1f9c 100644 --- a/spec/views/layouts/_head.html.haml_spec.rb +++ b/spec/views/layouts/_head.html.haml_spec.rb @@ -1,10 +1,6 @@ require 'spec_helper' describe 'layouts/_head' do - before do - stub_template 'layouts/_user_styles.html.haml' => '' - end - it 'escapes HTML-safe strings in page_title' do stub_helper_with_safe_string(:page_title) -- cgit v1.2.1 From 847ada36c48107442f69338eda4c0b601ab98b48 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 23 Nov 2016 19:18:34 +0200 Subject: Fix: Timeout creating and viewing merge request for binary file --- .../unreleased/timeout-merge-request-for-binary-file.yml | 4 ++++ lib/gitlab/diff/file_collection/merge_request_diff.rb | 6 +++--- .../gitlab/diff/file_collection/merge_request_diff_spec.rb | 13 +++++++++++++ .../merge_requests/merge_request_diff_cache_service_spec.rb | 1 + 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/timeout-merge-request-for-binary-file.yml create mode 100644 spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb diff --git a/changelogs/unreleased/timeout-merge-request-for-binary-file.yml b/changelogs/unreleased/timeout-merge-request-for-binary-file.yml new file mode 100644 index 00000000000..5161265d1bd --- /dev/null +++ b/changelogs/unreleased/timeout-merge-request-for-binary-file.yml @@ -0,0 +1,4 @@ +--- +title: Timeout creating and viewing merge request for binary file +merge_request: +author: diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index fe7adb7bed6..26bb0bc16f5 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -20,7 +20,7 @@ module Gitlab # Extracted method to highlight in the same iteration to the diff_collection. def decorate_diff!(diff) diff_file = super - cache_highlight!(diff_file) if cacheable? + cache_highlight!(diff_file) if cacheable?(diff_file) diff_file end @@ -60,8 +60,8 @@ module Gitlab Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty end - def cacheable? - @merge_request_diff.present? + def cacheable?(diff_file) + @merge_request_diff.present? && diff_file.blob.text? end def cache_key diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb new file mode 100644 index 00000000000..c863a5f04cc --- /dev/null +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Gitlab::Diff::FileCollection::MergeRequestDiff do + let(:merge_request) { create :merge_request } + + it 'does not hightlight binary files' do + allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => false)) + + expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) + + described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files + end +end diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb index 807f89e80b7..05cdbe5287a 100644 --- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb +++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb @@ -10,6 +10,7 @@ describe MergeRequests::MergeRequestDiffCacheService do expect(Rails.cache).to receive(:read).with(cache_key).and_return({}) expect(Rails.cache).to receive(:write).with(cache_key, anything) + allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => true)) subject.execute(merge_request) end -- cgit v1.2.1 From 07c3d2cafab4045f19849217f7e2e1a2b3a708d0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 25 Nov 2016 14:41:16 +0100 Subject: Refactor CI variables docs --- doc/ci/README.md | 6 ++- doc/ci/variables/README.md | 113 ++++++++++++++++++++++++++++----------------- doc/ci/yaml/README.md | 16 +++++-- 3 files changed, 86 insertions(+), 49 deletions(-) diff --git a/doc/ci/README.md b/doc/ci/README.md index 545cc72682d..f7fb56cb76a 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -11,7 +11,7 @@ - [Configure a Runner, the application that runs your builds](runners/README.md) - [Use Docker images with GitLab Runner](docker/using_docker_images.md) - [Use CI to build Docker images](docker/using_docker_build.md) -- [Use variables in your `.gitlab-ci.yml`](variables/README.md) +- [Learn how to use variables in your build scripts](variables/README.md) - [Use SSH keys in your build environment](ssh_keys/README.md) - [Trigger builds through the API](triggers/README.md) - [Build artifacts](../user/project/builds/artifacts.md) @@ -24,4 +24,6 @@ ## Breaking changes -- [New CI build permissions model](../user/project/new_ci_build_permissions_model.md) Read about what changed in GitLab 8.12 and how that affects your builds. There's a new way to access your Git submodules and LFS objects in builds. +- [New CI build permissions model](../user/project/new_ci_build_permissions_model.md) + Read about what changed in GitLab 8.12 and how that affects your builds. + There's a new way to access your Git submodules and LFS objects in builds. diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index a4c3a731a20..bb0b171838b 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -1,22 +1,27 @@ -## Variables +# Variables When receiving a build from GitLab CI, the runner prepares the build environment. -It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables** +It starts by setting a list of **predefined variables** (Environment variables) +and a list of **user-defined variables**. -The variables can be overwritten. They take precedence over each other in this order: -1. Trigger variables +The variables can be overwritten. They take precedence over each other in this +order: + +1. Trigger variables (take precedence over all) 1. Secure variables 1. YAML-defined job-level variables 1. YAML-defined global variables -1. Predefined variables +1. Predefined variables (are the lowest in the chain) -For example, if you define: -1. `API_TOKEN=SECURE` as Secure Variable -1. `API_TOKEN=YAML` as YAML-defined variable +For example, if you define `API_TOKEN=secure` as a secure variable and +`API_TOKEN=yaml` as YAML-defined variable, the `API_TOKEN` will take the value +`secure` as the secure variables are higher in the chain. -The `API_TOKEN` will take the Secure Variable value: `SECURE`. +## Predefined variables (Environment variables) -### Predefined variables (Environment Variables) +>**Note:** +Some of the variables are available only if a minimum version of [GitLab Runner] +is used. | Variable | GitLab | Runner | Description | |-------------------------|--------|--------|-------------| @@ -52,7 +57,6 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | -**Some of the variables are only available when using runner with at least defined version.** Example values: @@ -87,27 +91,61 @@ export GITLAB_USER_ID="42" export GITLAB_USER_EMAIL="alexzander@sporer.com" ``` -### YAML-defined variables -**This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher ** +## YAML-defined variables + +>**Note:** +This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher. -GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment. -The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL. +GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in the +build environment. The variables are hence saved in the repository, and they +are meant to store non-sensitive project configuration, e.g., `RAILS_ENV` or +`DATABASE_URL`. + +For example, if you set the variable below globally (not inside a job), it will +be used in all executed commands and scripts: ```yaml variables: DATABASE_URL: "postgres://postgres@postgres/my_database" ``` -These variables can be later used in all executed commands and scripts. +The YAML-defined variables are also set to all created +[service containers](../docker/using_docker_images.md), thus allowing to fine +tune them. + +Variables can be defined at a global level, but also at a job level. To turn off +global defined variables in your job, define an empty array: + +```yaml +job_name: + variables: [] +``` + +## User-defined variables (secure variables) -The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. +>**Notes:** +- This feature requires GitLab Runner 0.4.0 or higher. +- Be aware that secure variables are not masked, and their values can be shown + in the build logs if explicitly asked to do so. If your project is public or + internal, you can set the pipelines private from your project's Pipelines + settings. Follow the discussion in issue [#13784][ce-13784] for masking the + secure variables. -Variables can be defined at a global level, but also at a job level. +GitLab CI allows you to define per-project **Secure variables** that are set in +the build environment. The secure variables are stored out of the repository +(`.gitlab-ci.yml`) and are securely passed to GitLab Runner making them +available in the build environment. It's the recommended method to use for +storing things like passwords, secret keys and credentials. -More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +Secure Variables can added by going to your project's +**Settings ➔ Variables ➔ Add variable**. -#### Debug tracing +Once you set them, they will be available for all subsequent builds. +## Debug tracing + +> Introduced in GitLab Runner 1.7. +> > **WARNING:** Enabling debug tracing can have severe security implications. The output **will** contain the content of all your secure variables and any other secrets! The output **will** be uploaded to the GitLab server and made visible @@ -131,7 +169,7 @@ before making them visible again. To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: ```yaml -job1: +job_name: variables: CI_DEBUG_TRACE: "true" ``` @@ -139,40 +177,31 @@ job1: The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) demonstrates a working configuration, including build trace examples. -### User-defined variables (Secure Variables) -**This feature requires GitLab Runner 0.4.0 or higher** +## Using the CI variables in your job scripts -GitLab CI allows you to define per-project **Secure Variables** that are set in -the build environment. -The secure variables are stored out of the repository (the `.gitlab-ci.yml`). -The variables are securely passed to GitLab Runner and are available in the -build environment. -It's desired method to use them for storing passwords, secret keys or whatever -you want. +All variables are set as environment variables in the build environment, and +they are accessible with normal methods that are used to access such variables. +In most cases `bash` or `sh` is used to execute the build script. -**The value of the variable can be shown in build log if explicitly asked to do so.** -If your project is public or internal you can make the builds private. +To access the variables (predefined and user-defined) in a `bash`/`sh` environment, +prefix the variable name with the dollar sign (`$`): -Secure Variables can added by going to `Project > Variables > Add Variable`. - -They will be available for all subsequent builds. - -### Use variables -The variables are set as environment variables in build environment and are accessible with normal methods that are used to access such variables. -In most cases the **bash** is used to execute build script. -To access variables (predefined and user-defined) in bash environment, prefix the variable name with `$`: ``` job_name: script: - echo $CI_BUILD_ID ``` -You can also list all environment variables with `export` command, -but be aware that this will also expose value of all **Secure Variables** in build log: +You can also list all environment variables with the `export` command, +but be aware that this will also expose the values of all the secure variables +you set, in the build log: + ``` job_name: script: - export ``` +[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 +[gitlab runner]: https://docs.gitlab.com/runner/ [triggered]: ../triggers/README.md diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 338c9a27789..5f88974d360 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -406,14 +406,20 @@ except master. ### job variables It is possible to define build variables using a `variables` keyword on a job -level. It works basically the same way as its global-level equivalent but -allows you to define job-specific build variables. +level. It works basically the same way as its [global-level equivalent](#variables) +but allows you to define job-specific build variables. When the `variables` keyword is used on a job level, it overrides global YAML -build variables and predefined variables. +build variables and predefined variables. To turn off global defined variables +in your job, define an empty array: -Build variables priority is defined in -[variables documentation](../variables/README.md). +```yaml +job_name: + variables: [] +``` + +Build variables priority is defined in the +[variables documentation][variables]. ### tags -- cgit v1.2.1 From e1285c1d8ad2c7a5ed556bd5296fbe4afcacb16d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 15:11:56 +0100 Subject: Restore method that ensures builds being created --- app/services/ci/process_pipeline_service.rb | 15 ++++++++ spec/factories/ci/pipelines.rb | 11 ++++++ spec/services/ci/process_pipeline_service_spec.rb | 47 +++++++++++++---------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index e6bd1d1460c..2e028c44d8b 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -5,6 +5,8 @@ module Ci def execute(pipeline) @pipeline = pipeline + ensure_created_builds! # TODO, remove me in 9.0 + new_builds = stage_indexes_of_created_builds.map do |index| process_stage(index) @@ -67,5 +69,18 @@ module Ci def created_builds pipeline.builds.created end + + # This method is DEPRECATED and should be removed in 9.0. + # + # We need it to maintain backwards compatibility with previous versions + # when builds were not created within one transaction with the pipeline. + # + def ensure_created_builds! + return if created_builds.any? + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end end end diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index ac2a1ba5dff..3c35dae8772 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -29,5 +29,16 @@ FactoryGirl.define do allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } end end + + factory(:ci_pipeline_with_yaml) do + transient { yaml nil } + + after(:build) do |pipeline, evaluator| + raise ArgumentError unless evaluator.yaml + + allow(pipeline).to receive(:ci_yaml_file) + .and_return(YAML.dump(evaluator.yaml)) + end + end end end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index b130f876259..306943a5488 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::ProcessPipelineService, services: true do - let(:pipeline) { create(:ci_pipeline, ref: 'master') } + let(:pipeline) { create(:ci_empty_pipeline, ref: 'master') } let(:user) { create(:user) } describe '#execute' do @@ -293,57 +293,62 @@ describe Ci::ProcessPipelineService, services: true do end end - context 'when there are builds in multiple stages' do + context 'when there are builds that are not created yet' do + let(:pipeline) do + create(:ci_pipeline_with_yaml, yaml: config) + end + + let(:config) do + { rspec: { stage: 'test', script: 'rspec' }, + deploy: { stage: 'deploy', script: 'rsync' } } + end + before do create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0) create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0) - create(:ci_build, :created, pipeline: pipeline, name: 'rspec', stage: 'test', stage_idx: 1) - create(:ci_build, :created, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 1) - create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 2) end it 'processes the pipeline' do # Currently we have five builds with state created # expect(builds.count).to eq(0) - expect(all_builds.count).to eq(5) + expect(all_builds.count).to eq(2) - # Process builds will mark the created as pending + # Process builds service will enqueue builds from the first stage. # process_pipeline expect(builds.count).to eq(2) - expect(all_builds.count).to eq(5) + expect(all_builds.count).to eq(2) - # When builds succeed we will enqueue remaining builds - # We will have 2 succeeded, 2 pending (from stage test), - # total 5 (one more build from deploy) + # When builds succeed we will enqueue remaining builds. + # + # We will have 2 succeeded, 1 pending (from stage test), total 4 (two + # additional build from `.gitlab-ci.yml`). # succeed_pending process_pipeline expect(builds.success.count).to eq(2) - expect(builds.pending.count).to eq(2) - expect(all_builds.count).to eq(5) + expect(builds.pending.count).to eq(1) + expect(all_builds.count).to eq(4) - # When we succeed the 2 pending from stage test, - # We will queue a deploy stage. + # When pending build succeeds in stage test, we enqueue deploy stage. # succeed_pending process_pipeline expect(builds.pending.count).to eq(1) - expect(builds.success.count).to eq(4) - expect(all_builds.count).to eq(5) + expect(builds.success.count).to eq(3) + expect(all_builds.count).to eq(4) - # When we succeed last pending build, we will have - # a total of 5 succeeded builds + # When the last one succeeds we have 4 successful builds. # succeed_pending process_pipeline - expect(builds.success.count).to eq(5) - expect(all_builds.count).to eq(5) + expect(builds.success.count).to eq(4) + expect(all_builds.count).to eq(4) end end end -- cgit v1.2.1 From d766ab9f9a72a2919fb20c27fdf7b74e88042340 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 15:13:15 +0100 Subject: Remove pipeline factory that is not used in tests --- spec/factories/ci/pipelines.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 3c35dae8772..4fd29806590 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -18,12 +18,6 @@ FactoryGirl.define do end end - factory :ci_pipeline_with_two_job do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } }) } - end - end - factory :ci_pipeline do after(:build) do |commit| allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } -- cgit v1.2.1 From 86e7f22b6d10b9113c52f79685edb332d202f567 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 25 Nov 2016 15:15:31 +0100 Subject: Improve readability in pipeline test objects factory --- spec/factories/ci/pipelines.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 4fd29806590..23585db6ebd 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -7,20 +7,24 @@ FactoryGirl.define do project factory: :empty_project factory :ci_pipeline_without_jobs do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({}) } + after(:build) do |pipeline| + allow(pipeline).to receive(:ci_yaml_file) { YAML.dump({}) } end end factory :ci_pipeline_with_one_job do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" } }) } + after(:build) do |pipeline| + allow(pipeline).to receive(:ci_yaml_file) do + YAML.dump({ rspec: { script: "ls" } }) + end end end factory :ci_pipeline do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } + after(:build) do |pipeline| + allow(pipeline).to receive(:ci_yaml_file) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end end end -- cgit v1.2.1 From 6a08de7386fd41c9bbe27c32338328c6e6b40027 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 25 Nov 2016 15:41:28 +0100 Subject: Add issue search slash command One of many requested in: gitlab-org/gitlab-ce#24768 --- .../unreleased/zj-issue-search-slash-command.yml | 4 ++ lib/gitlab/chat_commands/base_command.rb | 4 +- lib/gitlab/chat_commands/command.rb | 1 + lib/gitlab/chat_commands/issue_command.rb | 6 +-- lib/gitlab/chat_commands/issue_search.rb | 17 ++++++++ spec/lib/gitlab/chat_commands/issue_search_spec.rb | 46 ++++++++++++++++++++++ spec/requests/api/services_spec.rb | 2 +- 7 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/zj-issue-search-slash-command.yml create mode 100644 lib/gitlab/chat_commands/issue_search.rb create mode 100644 spec/lib/gitlab/chat_commands/issue_search_spec.rb diff --git a/changelogs/unreleased/zj-issue-search-slash-command.yml b/changelogs/unreleased/zj-issue-search-slash-command.yml new file mode 100644 index 00000000000..de41c39d545 --- /dev/null +++ b/changelogs/unreleased/zj-issue-search-slash-command.yml @@ -0,0 +1,4 @@ +--- +title: Add issue search slash command +merge_request: +author: diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index e59d69b72b9..25da8474e95 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -40,9 +40,7 @@ module Gitlab private def find_by_iid(iid) - resource = collection.find_by(iid: iid) - - readable?(resource) ? resource : nil + collection.find_by(iid: iid) end end end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index 0ec358debc7..b0d3fdbc48a 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -4,6 +4,7 @@ module Gitlab COMMANDS = [ Gitlab::ChatCommands::IssueShow, Gitlab::ChatCommands::IssueCreate, + Gitlab::ChatCommands::IssueSearch, Gitlab::ChatCommands::Deploy, ].freeze diff --git a/lib/gitlab/chat_commands/issue_command.rb b/lib/gitlab/chat_commands/issue_command.rb index f1bc36239d5..84de3e44c70 100644 --- a/lib/gitlab/chat_commands/issue_command.rb +++ b/lib/gitlab/chat_commands/issue_command.rb @@ -6,11 +6,7 @@ module Gitlab end def collection - project.issues - end - - def readable?(issue) - self.class.can?(current_user, :read_issue, issue) + IssuesFinder.new(current_user, project_id: project.id).execute end end end diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb new file mode 100644 index 00000000000..51bf80c800b --- /dev/null +++ b/lib/gitlab/chat_commands/issue_search.rb @@ -0,0 +1,17 @@ +module Gitlab + module ChatCommands + class IssueSearch < IssueCommand + def self.match(text) + /\Aissue\s+search\s+(?.*)/.match(text) + end + + def self.help_message + "issue search " + end + + def execute(match) + collection.search(match[:query]).limit(QUERY_LIMIT) + end + end + end +end diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb new file mode 100644 index 00000000000..24c06a967fa --- /dev/null +++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::ChatCommands::IssueSearch, service: true do + describe '#execute' do + let!(:issue) { create(:issue, title: 'find me') } + let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } + let(:project) { issue.project } + let(:user) { issue.author } + let(:regex_match) { described_class.match("issue search find") } + + subject do + described_class.new(project, user).execute(regex_match) + end + + context 'when the user has no access' do + it 'only returns the open issues' do + expect(subject).not_to include(confidential) + end + end + + context 'the user has access' do + before do + project.team << [user, :master] + end + + it 'returns all results' do + expect(subject).to include(confidential, issue) + end + end + + context 'without hits on the query' do + it 'returns an empty collection' do + expect(subject).to be_empty + end + end + end + + describe 'self.match' do + let(:query) { "my search keywords" } + it 'matches the query' do + match = described_class.match("issue search #{query}") + + expect(match[:query]).to eq(query) + end + end +end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index ce9c96ace21..bb0344e5995 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -128,7 +128,7 @@ describe API::API, api: true do ) end - it 'retusn status 200' do + it 'returns status 200' do post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params expect(response).to have_http_status(200) -- cgit v1.2.1 From 40e8185b64a04f719c85a793d0fdd5438a129975 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 22 Nov 2016 17:28:58 +0100 Subject: Expose coverage on GET pipelines/:id The coverage wasn't exposed yet, now it is but only for detailed requests to save queries on the database. --- changelogs/unreleased/zj-expose-coverage-pipelines.yml | 4 ++++ lib/api/entities.rb | 1 + spec/requests/api/pipelines_spec.rb | 12 ++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 changelogs/unreleased/zj-expose-coverage-pipelines.yml diff --git a/changelogs/unreleased/zj-expose-coverage-pipelines.yml b/changelogs/unreleased/zj-expose-coverage-pipelines.yml new file mode 100644 index 00000000000..34e4926e58a --- /dev/null +++ b/changelogs/unreleased/zj-expose-coverage-pipelines.yml @@ -0,0 +1,4 @@ +--- +title: 'API: expose pipeline coverage' +merge_request: +author: diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 33cb6fd3704..7dfaf2e8eb7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -606,6 +606,7 @@ module API expose :user, with: Entities::UserBasic expose :created_at, :updated_at, :started_at, :finished_at, :committed_at expose :duration + expose :coverage end class EnvironmentBasic < Grape::Entity diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index d83f7883c78..a7e511aaeda 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -103,6 +103,18 @@ describe API::API, api: true do expect(json_response['message']).to eq '404 Not found' expect(json_response['id']).to be nil end + + context 'with coverage' do + before do + create(:ci_build, coverage: 30, pipeline: pipeline) + end + + it 'exposes the coverage' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) + + expect(json_response["coverage"].to_i).to eq(30) + end + end end context 'unauthorized user' do -- cgit v1.2.1 From 38ed96e9b1a47dca5aa2590fa9b0ade908337435 Mon Sep 17 00:00:00 2001 From: hhoopes Date: Wed, 17 Aug 2016 16:27:01 -0600 Subject: Add diff hunks to notification emails on MR Added diff hunks to notification emails of messages on merge requests. This provides code context to the note. Uses existing template for formatting a diff for email (from repository push notifications). --- CHANGELOG.md | 1 + .../mailers/highlighted_diff_email.scss | 143 +++++++++++++++++++++ .../stylesheets/mailers/repository_push_email.scss | 143 --------------------- .../notify/note_merge_request_email.html.haml | 8 ++ app/views/notify/repository_push_email.html.haml | 2 +- spec/mailers/notify_spec.rb | 19 ++- 6 files changed, 170 insertions(+), 146 deletions(-) create mode 100644 app/assets/stylesheets/mailers/highlighted_diff_email.scss delete mode 100644 app/assets/stylesheets/mailers/repository_push_email.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 549336e4dff..56f749e94ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -797,6 +797,7 @@ entry. ## 8.11.0 (2016-08-22) - Use test coverage value from the latest successful pipeline in badge. !5862 + - Add git diff context to notifications of new notes on merge requests !5855 (hoopes) - Add test coverage report badge. !5708 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Add Koding (online IDE) integration diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss new file mode 100644 index 00000000000..8d1a6020ca4 --- /dev/null +++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss @@ -0,0 +1,143 @@ +@import "framework/variables"; + +// This file is largely copied from `highlight/white.scss`, but modified to +// avoid all descendant selectors (`table td`). This is because the CSS inlining +// we use performs dramatically worse on descendant selectors than the +// alternatives. +// +// +// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of +// preference): plain class selectors, type (element name) selectors, or +// explicit child selectors. + +.code { + background-color: #fff; + font-family: monospace; + font-size: $code_font_size; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + + > tr { + line-height: $code_line_height; + } +} + +.diff-line-num { + padding: 0 5px; + text-align: right; + width: 35px; + background-color: $background-color; + color: $black-transparent; + border-right: 1px solid $table-border-gray; + + &.old { + background-color: $line-number-old; + border-right-color: $line-removed-dark; + } + + &.new { + background-color: $line-number-new; + border-right-color: $line-added-dark; + } +} + +.line_content { + padding-left: 0.5em; + padding-right: 0.5em; + + &.old { + background-color: $line-removed; + + > .line > span.idiff, + > .line > span > span.idiff { + background-color: $line-removed-dark; + } + } + + &.new { + background-color: $line-added; + + > .line > span.idiff, + > .line > span > span.idiff { + background-color: $line-added-dark; + } + } + + &.match { + color: $black-transparent; + background-color: $match-line; + } +} + +pre { + margin: 0; +} + +span.highlight_word { + background-color: #fafe3d !important; +} + +.hll { background-color: #f8f8f8; } +.c { color: #998; font-style: italic; } +.err { color: #a61717; background-color: #e3d2d2; } +.k { font-weight: bold; } +.o { font-weight: bold; } +.cm { color: #998; font-style: italic; } +.cp { color: #999; font-weight: bold; } +.c1 { color: #998; font-style: italic; } +.cs { color: #999; font-weight: bold; font-style: italic; } +.gd { color: #000; background-color: #fdd; } +.gd .x { color: #000; background-color: #faa; } +.ge { font-style: italic; } +.gr { color: #a00; } +.gh { color: #999; } +.gi { color: #000; background-color: #dfd; } +.gi .x { color: #000; background-color: #afa; } +.go { color: #888; } +.gp { color: #555; } +.gs { font-weight: bold; } +.gu { color: #800080; font-weight: bold; } +.gt { color: #a00; } +.kc { font-weight: bold; } +.kd { font-weight: bold; } +.kn { font-weight: bold; } +.kp { font-weight: bold; } +.kr { font-weight: bold; } +.kt { color: #458; font-weight: bold; } +.m { color: #099; } +.s { color: #d14; } +.n { color: #333; } +.na { color: teal; } +.nb { color: #0086b3; } +.nc { color: #458; font-weight: bold; } +.no { color: teal; } +.ni { color: purple; } +.ne { color: #900; font-weight: bold; } +.nf { color: #900; font-weight: bold; } +.nn { color: #555; } +.nt { color: navy; } +.nv { color: teal; } +.ow { font-weight: bold; } +.w { color: #bbb; } +.mf { color: #099; } +.mh { color: #099; } +.mi { color: #099; } +.mo { color: #099; } +.sb { color: #d14; } +.sc { color: #d14; } +.sd { color: #d14; } +.s2 { color: #d14; } +.se { color: #d14; } +.sh { color: #d14; } +.si { color: #d14; } +.sx { color: #d14; } +.sr { color: #009926; } +.s1 { color: #d14; } +.ss { color: #990073; } +.bp { color: #999; } +.vc { color: teal; } +.vg { color: teal; } +.vi { color: teal; } +.il { color: #099; } +.gc { color: #999; background-color: #eaf2f5; } diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss deleted file mode 100644 index 8d1a6020ca4..00000000000 --- a/app/assets/stylesheets/mailers/repository_push_email.scss +++ /dev/null @@ -1,143 +0,0 @@ -@import "framework/variables"; - -// This file is largely copied from `highlight/white.scss`, but modified to -// avoid all descendant selectors (`table td`). This is because the CSS inlining -// we use performs dramatically worse on descendant selectors than the -// alternatives. -// -// -// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of -// preference): plain class selectors, type (element name) selectors, or -// explicit child selectors. - -.code { - background-color: #fff; - font-family: monospace; - font-size: $code_font_size; - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 100%; - - > tr { - line-height: $code_line_height; - } -} - -.diff-line-num { - padding: 0 5px; - text-align: right; - width: 35px; - background-color: $background-color; - color: $black-transparent; - border-right: 1px solid $table-border-gray; - - &.old { - background-color: $line-number-old; - border-right-color: $line-removed-dark; - } - - &.new { - background-color: $line-number-new; - border-right-color: $line-added-dark; - } -} - -.line_content { - padding-left: 0.5em; - padding-right: 0.5em; - - &.old { - background-color: $line-removed; - - > .line > span.idiff, - > .line > span > span.idiff { - background-color: $line-removed-dark; - } - } - - &.new { - background-color: $line-added; - - > .line > span.idiff, - > .line > span > span.idiff { - background-color: $line-added-dark; - } - } - - &.match { - color: $black-transparent; - background-color: $match-line; - } -} - -pre { - margin: 0; -} - -span.highlight_word { - background-color: #fafe3d !important; -} - -.hll { background-color: #f8f8f8; } -.c { color: #998; font-style: italic; } -.err { color: #a61717; background-color: #e3d2d2; } -.k { font-weight: bold; } -.o { font-weight: bold; } -.cm { color: #998; font-style: italic; } -.cp { color: #999; font-weight: bold; } -.c1 { color: #998; font-style: italic; } -.cs { color: #999; font-weight: bold; font-style: italic; } -.gd { color: #000; background-color: #fdd; } -.gd .x { color: #000; background-color: #faa; } -.ge { font-style: italic; } -.gr { color: #a00; } -.gh { color: #999; } -.gi { color: #000; background-color: #dfd; } -.gi .x { color: #000; background-color: #afa; } -.go { color: #888; } -.gp { color: #555; } -.gs { font-weight: bold; } -.gu { color: #800080; font-weight: bold; } -.gt { color: #a00; } -.kc { font-weight: bold; } -.kd { font-weight: bold; } -.kn { font-weight: bold; } -.kp { font-weight: bold; } -.kr { font-weight: bold; } -.kt { color: #458; font-weight: bold; } -.m { color: #099; } -.s { color: #d14; } -.n { color: #333; } -.na { color: teal; } -.nb { color: #0086b3; } -.nc { color: #458; font-weight: bold; } -.no { color: teal; } -.ni { color: purple; } -.ne { color: #900; font-weight: bold; } -.nf { color: #900; font-weight: bold; } -.nn { color: #555; } -.nt { color: navy; } -.nv { color: teal; } -.ow { font-weight: bold; } -.w { color: #bbb; } -.mf { color: #099; } -.mh { color: #099; } -.mi { color: #099; } -.mo { color: #099; } -.sb { color: #d14; } -.sc { color: #d14; } -.sd { color: #d14; } -.s2 { color: #d14; } -.se { color: #d14; } -.sh { color: #d14; } -.si { color: #d14; } -.sx { color: #d14; } -.sr { color: #009926; } -.s1 { color: #d14; } -.ss { color: #990073; } -.bp { color: #999; } -.vc { color: teal; } -.vg { color: teal; } -.vi { color: teal; } -.il { color: #099; } -.gc { color: #999; background-color: #eaf2f5; } diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index ea7e3d199fd..de3f32e6415 100644 --- a/app/views/notify/note_merge_request_email.html.haml +++ b/app/views/notify/note_merge_request_email.html.haml @@ -1,7 +1,15 @@ += content_for :head do + = stylesheet_link_tag 'mailers/highlighted_diff_email' + - if @note.diff_note? && @note.diff_file %p.details New comment on diff for = link_to @note.diff_file.file_path, @target_url \: + .diff-content.code.js-syntax-highlight + %table + - Discussion.new([@note]).truncated_diff_lines.each do |line| + = render "projects/diffs/line", line: line, diff_file: @note.diff_file, plain: true + = render 'note_message' diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 307c5a11206..25883de257c 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -1,5 +1,5 @@ = content_for :head do - = stylesheet_link_tag 'mailers/repository_push_email' + = stylesheet_link_tag 'mailers/highlighted_diff_email' %h3 #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 932a5dc4862..b40a6b1cbac 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -580,8 +580,10 @@ describe Notify do let(:note_author) { create(:user, name: 'author_name') } let(:note) { create(:note, project: project, author: note_author) } - before :each do - allow(Note).to receive(:find).with(note.id).and_return(note) + before do |example| + unless example.metadata[:skip_before] + allow(Note).to receive(:find).with(note.id).and_return(note) + end end shared_examples 'a note email' do @@ -663,6 +665,19 @@ describe Notify do end end + describe "on a merge request with diffs", :skip_before do + let(:merge_request) { create(:merge_request_with_diffs) } + let(:note_with_diff) {create(:diff_note_on_merge_request)} + + before(:each) { allow(note_with_diff).to receive(:noteable).and_return(merge_request) } + subject { Notify.note_merge_request_email(recipient.id, note_with_diff.id) } + + it 'includes diffs with character-level highlighting' do + expected_line_text = Discussion.new([note_with_diff]).truncated_diff_lines.first.text + is_expected.to have_body_text expected_line_text + end + end + describe 'on an issue' do let(:issue) { create(:issue, project: project) } let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } -- cgit v1.2.1 From 24070bac45134c915c13d3e94723a44f59ab4e3a Mon Sep 17 00:00:00 2001 From: hhoopes Date: Mon, 22 Aug 2016 23:36:30 -0600 Subject: Add new template to handle both commit & mr notes Currently comments on commits and merge requests do not require merge request- or commit-specific information, but can use the same template. Rather than change the method which calls the template, I opted to keep the templates separate and create a new template to highlight their identicality, while preserving the option to distinguish them from each other in the future. Also removed some of the inconsistencies between text and html email versions. Still needed is a text-only version of git diffs and testing. --- app/mailers/emails/notes.rb | 2 ++ .../notify/_note_mr_or_commit_email.html.haml | 16 ++++++++++++++++ app/views/notify/note_commit_email.html.haml | 9 +++++++-- app/views/notify/note_commit_email.text.erb | 6 +++--- .../notify/note_merge_request_email.html.haml | 22 +++++++--------------- app/views/notify/note_merge_request_email.text.erb | 6 +++--- spec/mailers/notify_spec.rb | 19 ++----------------- 7 files changed, 40 insertions(+), 40 deletions(-) create mode 100644 app/views/notify/_note_mr_or_commit_email.html.haml diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 96116e916dd..0d20c9092c4 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,6 +4,7 @@ module Emails setup_note_mail(note_id, recipient_id) @commit = @note.noteable + @discussion = @note.to_discussion if @note.diff_note? @target_url = namespace_project_commit_url(*note_target_url_options) mail_answer_thread(@commit, @@ -24,6 +25,7 @@ module Emails setup_note_mail(note_id, recipient_id) @merge_request = @note.noteable + @discussion = @note.to_discussion if @note.diff_note? @target_url = namespace_project_merge_request_url(*note_target_url_options) mail_answer_thread(@merge_request, note_thread_options(recipient_id)) end diff --git a/app/views/notify/_note_mr_or_commit_email.html.haml b/app/views/notify/_note_mr_or_commit_email.html.haml new file mode 100644 index 00000000000..3e2046aa9cf --- /dev/null +++ b/app/views/notify/_note_mr_or_commit_email.html.haml @@ -0,0 +1,16 @@ += content_for :head do + = stylesheet_link_tag 'mailers/highlighted_diff_email' + +- if note.diff_note? && note.diff_file + = link_to note.diff_file.file_path, @target_url, class: 'details' + \: + + %table + = render partial: "projects/diffs/line", + collection: discussion.truncated_diff_lines, + as: :line, + locals: { diff_file: note.diff_file, + plain: true, + email: true } + += render 'note_message' diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml index 1d961e4424c..f4293a3aa50 100644 --- a/app/views/notify/note_commit_email.html.haml +++ b/app/views/notify/note_commit_email.html.haml @@ -1,2 +1,7 @@ -= render 'note_message' - +%p.details + New comment for Commit + = @commit.short_id + on + = render partial: 'note_mr_or_commit_email', + locals: { note: @note, + discussion: @discussion} diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb index aaeaf5fdf73..a1ef9858021 100644 --- a/app/views/notify/note_commit_email.text.erb +++ b/app/views/notify/note_commit_email.text.erb @@ -2,8 +2,8 @@ New comment for Commit <%= @commit.short_id %> <%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> - -Author: <%= @note.author_name %> +<% if current_application_settings.email_author_in_body %> + <%= @note.author_name %> wrote: +<% end %> <%= @note.note %> - diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index de3f32e6415..75250b0383d 100644 --- a/app/views/notify/note_merge_request_email.html.haml +++ b/app/views/notify/note_merge_request_email.html.haml @@ -1,15 +1,7 @@ -= content_for :head do - = stylesheet_link_tag 'mailers/highlighted_diff_email' - -- if @note.diff_note? && @note.diff_file - %p.details - New comment on diff for - = link_to @note.diff_file.file_path, @target_url - \: - - .diff-content.code.js-syntax-highlight - %table - - Discussion.new([@note]).truncated_diff_lines.each do |line| - = render "projects/diffs/line", line: line, diff_file: @note.diff_file, plain: true - -= render 'note_message' +%p.details + New comment for Merge Request + = @merge_request.to_reference + on + = render partial: 'note_mr_or_commit_email', + locals: { note: @note, + discussion: @discussion} diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index 8cdab63829e..42d29b34ebc 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -2,8 +2,8 @@ New comment for Merge Request <%= @merge_request.to_reference %> <%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> - -<%= @note.author_name %> +<% if current_application_settings.email_author_in_body %> + <%= @note.author_name %> wrote: +<% end %> <%= @note.note %> - diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index b40a6b1cbac..932a5dc4862 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -580,10 +580,8 @@ describe Notify do let(:note_author) { create(:user, name: 'author_name') } let(:note) { create(:note, project: project, author: note_author) } - before do |example| - unless example.metadata[:skip_before] - allow(Note).to receive(:find).with(note.id).and_return(note) - end + before :each do + allow(Note).to receive(:find).with(note.id).and_return(note) end shared_examples 'a note email' do @@ -665,19 +663,6 @@ describe Notify do end end - describe "on a merge request with diffs", :skip_before do - let(:merge_request) { create(:merge_request_with_diffs) } - let(:note_with_diff) {create(:diff_note_on_merge_request)} - - before(:each) { allow(note_with_diff).to receive(:noteable).and_return(merge_request) } - subject { Notify.note_merge_request_email(recipient.id, note_with_diff.id) } - - it 'includes diffs with character-level highlighting' do - expected_line_text = Discussion.new([note_with_diff]).truncated_diff_lines.first.text - is_expected.to have_body_text expected_line_text - end - end - describe 'on an issue' do let(:issue) { create(:issue, project: project) } let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } -- cgit v1.2.1 From f928dba99b0550cefa7534d7fd5bd1ea16609274 Mon Sep 17 00:00:00 2001 From: hhoopes Date: Thu, 25 Aug 2016 10:38:07 -0600 Subject: Change diff highlight/truncate for reusability Previously the `truncated_diff_lines` method for outputting a discussion diff took in already highlighted lines, which meant it wasn't reuseable for truncating ANY lines. In the way it was used, it also meant that for any email truncation, the whole diff was being highlighted before being truncated, meaning wasted time highlighting lines that wouldn't even be used (granted, they were being memoized, so perhaps this wasn't that great of an issue). I refactored truncation away from highlighting, in order to truncate formatted diffs for text templates in email, using `>`s to designate each line, but otherwise retaining the parsing already done to create `diff_lines`. Additionally, while notes on merge requests or commits had already been tested, there was no existing test for notes on a diff on an MR or commit. Added mailer tests for such, and a unit test for truncating diff lines. --- app/models/discussion.rb | 12 ++-- app/views/discussions/_diff_with_notes.html.haml | 2 +- .../notify/_note_mr_or_commit_email.html.haml | 9 +-- app/views/notify/_simple_diff.text.erb | 4 ++ app/views/notify/note_commit_email.html.haml | 6 +- app/views/notify/note_commit_email.text.erb | 2 + .../notify/note_merge_request_email.html.haml | 6 +- app/views/notify/note_merge_request_email.text.erb | 2 + lib/gitlab/diff/file.rb | 13 +++- spec/mailers/notify_spec.rb | 80 +++++++++++++++++++++- spec/models/discussion_spec.rb | 25 +++++++ 11 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 app/views/notify/_simple_diff.text.erb diff --git a/app/models/discussion.rb b/app/models/discussion.rb index de06c13481a..486bfd2c766 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -25,7 +25,12 @@ class Discussion to: :last_resolved_note, allow_nil: true - delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true + delegate :blob, + :highlighted_diff_lines, + :text_parsed_diff_lines, + + to: :diff_file, + allow_nil: true def self.for_notes(notes) notes.group_by(&:discussion_id).values.map { |notes| new(notes) } @@ -162,18 +167,15 @@ class Discussion def truncated_diff_lines prev_lines = [] - highlighted_diff_lines.each do |line| + diff_file.diff_lines.each do |line| if line.meta? prev_lines.clear else prev_lines << line - break if for_line?(line) - prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES end end - prev_lines end diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 3a95a652810..06493ba0105 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -9,7 +9,7 @@ %table - discussions = { discussion.original_line_code => discussion } = render partial: "projects/diffs/line", - collection: discussion.truncated_diff_lines, + collection: discussion.highlighted_diff_lines(discussion.truncated_diff_lines), as: :line, locals: { diff_file: diff_file, discussions: discussions, diff --git a/app/views/notify/_note_mr_or_commit_email.html.haml b/app/views/notify/_note_mr_or_commit_email.html.haml index 3e2046aa9cf..7033842b557 100644 --- a/app/views/notify/_note_mr_or_commit_email.html.haml +++ b/app/views/notify/_note_mr_or_commit_email.html.haml @@ -1,15 +1,16 @@ = content_for :head do = stylesheet_link_tag 'mailers/highlighted_diff_email' -- if note.diff_note? && note.diff_file - = link_to note.diff_file.file_path, @target_url, class: 'details' +- if @note.diff_note? && @note.diff_file + on + = link_to @note.diff_file.file_path, @target_url, class: 'details' \: %table = render partial: "projects/diffs/line", - collection: discussion.truncated_diff_lines, + collection: @discussion.highlighted_diff_lines(@discussion.truncated_diff_lines), as: :line, - locals: { diff_file: note.diff_file, + locals: { diff_file: @note.diff_file, plain: true, email: true } diff --git a/app/views/notify/_simple_diff.text.erb b/app/views/notify/_simple_diff.text.erb new file mode 100644 index 00000000000..a5a796bc168 --- /dev/null +++ b/app/views/notify/_simple_diff.text.erb @@ -0,0 +1,4 @@ +<% lines = @discussion.truncated_diff_lines %> +<% @discussion.text_parsed_diff_lines(lines).each do |line| %> + <%= line %> +<% end %> diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml index f4293a3aa50..17dcf36689f 100644 --- a/app/views/notify/note_commit_email.html.haml +++ b/app/views/notify/note_commit_email.html.haml @@ -1,7 +1,5 @@ %p.details New comment for Commit = @commit.short_id - on - = render partial: 'note_mr_or_commit_email', - locals: { note: @note, - discussion: @discussion} + + = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb index a1ef9858021..715e58af61c 100644 --- a/app/views/notify/note_commit_email.text.erb +++ b/app/views/notify/note_commit_email.text.erb @@ -2,6 +2,8 @@ New comment for Commit <%= @commit.short_id %> <%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> +<%= render 'simple_diff' if @discussion %> + <% if current_application_settings.email_author_in_body %> <%= @note.author_name %> wrote: <% end %> diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index 75250b0383d..b7758f191dc 100644 --- a/app/views/notify/note_merge_request_email.html.haml +++ b/app/views/notify/note_merge_request_email.html.haml @@ -1,7 +1,5 @@ %p.details New comment for Merge Request = @merge_request.to_reference - on - = render partial: 'note_mr_or_commit_email', - locals: { note: @note, - discussion: @discussion} + + = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index 42d29b34ebc..d24e15af91f 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -2,6 +2,8 @@ New comment for Merge Request <%= @merge_request.to_reference %> <%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> +<%= render 'simple_diff' if @discussion %> + <% if current_application_settings.email_author_in_body %> <%= @note.author_name %> wrote: <% end %> diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index c6bf25b5874..9b60102947a 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -69,15 +69,22 @@ module Gitlab diff_refs.try(:head_sha) end - attr_writer :highlighted_diff_lines + attr_writer :highlighted_diff_lines, :text_parsed_diff_lines # Array of Gitlab::Diff::Line objects def diff_lines @diff_lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a end - def highlighted_diff_lines - @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight + def highlighted_diff_lines(lines = self) + @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(lines, repository: self.repository).highlight + end + + def text_parsed_diff_lines(lines) + @text_parsed_diff_lines ||= + lines.map do | line | + "> " + line.text + end end # Array[] with right/left keys that contains Gitlab::Diff::Line objects which text is hightlighted diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 932a5dc4862..a80eb114c17 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -646,7 +646,6 @@ describe Notify do before(:each) { allow(note).to receive(:noteable).and_return(merge_request) } subject { Notify.note_merge_request_email(recipient.id, note.id) } - it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do let(:model) { merge_request } @@ -686,6 +685,85 @@ describe Notify do end end end + + context 'items that are noteable, emails for a note on a diff' do + let(:note_author) { create(:user, name: 'author_name') } + + before :each do + allow(Note).to receive(:find).with(note.id).and_return(note) + end + + shared_examples 'a note email on a diff' do | model | + let(:note) { create(model, project: project, author: note_author) } + + it "includes diffs with character-level highlighting" do + is_expected.to have_body_text /\vars = {<\/span>/ + end + + it 'contains a link to the diff file' do + is_expected.to have_body_text /#{note.diff_file.file_path}/ + end + + it_behaves_like 'it should have Gmail Actions links' + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(note_author.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'is sent to the given recipient' do + is_expected.to deliver_to recipient.notification_email + end + + it 'contains the message from the note' do + is_expected.to have_body_text /#{note.note}/ + end + + it 'does not contain note author' do + is_expected.not_to have_body_text /wrote\:/ + end + + context 'when enabled email_author_in_body' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true) + end + + it 'contains a link to note author' do + is_expected.to have_body_text note.author_name + is_expected.to have_body_text /wrote\:/ + end + end + end + + describe 'on a commit' do + let(:commit) { project.commit } + let(:note) { create(:diff_note_on_commit) } + + subject { Notify.note_commit_email(recipient.id, note.id) } + + it_behaves_like 'a note email on a diff', :diff_note_on_commit + # it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + # let(:model) { commit } + # end + it_behaves_like 'it should show Gmail Actions View Commit link' + it_behaves_like 'a user cannot unsubscribe through footer link' + end + + describe 'on a merge request' do + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:note) { create(:diff_note_on_merge_request) } + + subject { Notify.note_merge_request_email(recipient.id, note.id) } + + it_behaves_like 'a note email on a diff', :diff_note_on_merge_request + # it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + # let(:model) { merge_request } + # end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' + end + end end context 'for a group' do diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index 0142706d140..d4b1f480c56 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -590,4 +590,29 @@ describe Discussion, model: true do end end end + + describe "#truncated_diff_lines" do + let(:truncated_lines) { subject.truncated_diff_lines } + + context "when diff is greater than allowed number of truncated diff lines " do + let(:initial_line_count) { subject.diff_file.diff_lines.count } + let(:truncated_line_count) { truncated_lines.count } + + it "returns fewer lines" do + expect(initial_line_count).to be > described_class::NUMBER_OF_TRUNCATED_DIFF_LINES + + expect(truncated_line_count).to be <= described_class::NUMBER_OF_TRUNCATED_DIFF_LINES + end + end + + context "when some diff lines are meta" do + let(:initial_meta_lines?) { subject.diff_file.diff_lines.any?(&:meta?) } + let(:truncated_meta_lines?) { truncated_lines.any?(&:meta?) } + + it "returns no meta lines" do + expect(initial_meta_lines?).to be true + expect(truncated_meta_lines?).to be false + end + end + end end -- cgit v1.2.1 From a761c59a6bfc4d66649910d01e4c8412bb0b40ec Mon Sep 17 00:00:00 2001 From: hhoopes Date: Wed, 31 Aug 2016 10:18:26 -0600 Subject: Add keyword arguments to truncated_diff method * Added keyword arguments to truncated_diff_lines method to allow for using highlighting or not (html templates vs. text) * Tweaked templates for consistency and format appropriateness --- app/models/discussion.rb | 7 ++++--- app/views/discussions/_diff_with_notes.html.haml | 2 +- app/views/notify/_note_message.text.erb | 5 +++++ app/views/notify/_note_mr_or_commit_email.html.haml | 11 +++++++---- app/views/notify/_note_mr_or_commit_email.text.erb | 8 ++++++++ app/views/notify/_simple_diff.text.erb | 5 ++--- app/views/notify/note_commit_email.html.haml | 3 --- app/views/notify/note_commit_email.text.erb | 13 +++---------- app/views/notify/note_merge_request_email.html.haml | 3 --- app/views/notify/note_merge_request_email.text.erb | 13 +++---------- lib/gitlab/diff/file.rb | 11 ++--------- spec/mailers/notify_spec.rb | 8 +------- spec/models/discussion_spec.rb | 4 ++-- 13 files changed, 38 insertions(+), 55 deletions(-) create mode 100644 app/views/notify/_note_message.text.erb create mode 100644 app/views/notify/_note_mr_or_commit_email.text.erb diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 486bfd2c766..9bd37fe6d89 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -27,7 +27,7 @@ class Discussion delegate :blob, :highlighted_diff_lines, - :text_parsed_diff_lines, + :diff_lines, to: :diff_file, allow_nil: true @@ -164,10 +164,11 @@ class Discussion end # Returns an array of at most 16 highlighted lines above a diff note - def truncated_diff_lines + def truncated_diff_lines(highlight: true) + initial_lines = highlight ? highlighted_diff_lines : diff_lines prev_lines = [] - diff_file.diff_lines.each do |line| + initial_lines.each do |line| if line.meta? prev_lines.clear else diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 06493ba0105..5c667e4842b 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -9,7 +9,7 @@ %table - discussions = { discussion.original_line_code => discussion } = render partial: "projects/diffs/line", - collection: discussion.highlighted_diff_lines(discussion.truncated_diff_lines), + collection: discussion.highlighted_diff_lines, as: :line, locals: { diff_file: diff_file, discussions: discussions, diff --git a/app/views/notify/_note_message.text.erb b/app/views/notify/_note_message.text.erb new file mode 100644 index 00000000000..f82cbc9a3fc --- /dev/null +++ b/app/views/notify/_note_message.text.erb @@ -0,0 +1,5 @@ +<% if current_application_settings.email_author_in_body %> + <%= @note.author_name %> wrote: +<% end -%> + +<%= @note.note %> diff --git a/app/views/notify/_note_mr_or_commit_email.html.haml b/app/views/notify/_note_mr_or_commit_email.html.haml index 7033842b557..15e92c42b14 100644 --- a/app/views/notify/_note_mr_or_commit_email.html.haml +++ b/app/views/notify/_note_mr_or_commit_email.html.haml @@ -1,17 +1,20 @@ = content_for :head do = stylesheet_link_tag 'mailers/highlighted_diff_email' +New comment + - if @note.diff_note? && @note.diff_file on = link_to @note.diff_file.file_path, @target_url, class: 'details' - \: +\: +- if @discussion %table = render partial: "projects/diffs/line", - collection: @discussion.highlighted_diff_lines(@discussion.truncated_diff_lines), + collection: @discussion.truncated_diff_lines, as: :line, locals: { diff_file: @note.diff_file, - plain: true, - email: true } + plain: true, + email: true } = render 'note_message' diff --git a/app/views/notify/_note_mr_or_commit_email.text.erb b/app/views/notify/_note_mr_or_commit_email.text.erb new file mode 100644 index 00000000000..3dd1b4d4c0e --- /dev/null +++ b/app/views/notify/_note_mr_or_commit_email.text.erb @@ -0,0 +1,8 @@ +<% if @note.diff_note? && @note.diff_file -%> + on <%= @note.diff_file.file_path -%> +<% end -%>: + +<%= url %> + +<%= render 'simple_diff' if @discussion -%> +<%= render 'note_message' %> diff --git a/app/views/notify/_simple_diff.text.erb b/app/views/notify/_simple_diff.text.erb index a5a796bc168..58b0018c0ca 100644 --- a/app/views/notify/_simple_diff.text.erb +++ b/app/views/notify/_simple_diff.text.erb @@ -1,4 +1,3 @@ -<% lines = @discussion.truncated_diff_lines %> -<% @discussion.text_parsed_diff_lines(lines).each do |line| %> - <%= line %> +<% @discussion.truncated_diff_lines(highlight: false).each do |line| %> + <%= "> " + line.text %> <% end %> diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml index 17dcf36689f..0a650e3b2ca 100644 --- a/app/views/notify/note_commit_email.html.haml +++ b/app/views/notify/note_commit_email.html.haml @@ -1,5 +1,2 @@ %p.details - New comment for Commit - = @commit.short_id - = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb index 715e58af61c..dc764b61659 100644 --- a/app/views/notify/note_commit_email.text.erb +++ b/app/views/notify/note_commit_email.text.erb @@ -1,11 +1,4 @@ -New comment for Commit <%= @commit.short_id %> +<% url = url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> -<%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> - -<%= render 'simple_diff' if @discussion %> - -<% if current_application_settings.email_author_in_body %> - <%= @note.author_name %> wrote: -<% end %> - -<%= @note.note %> +New comment for Commit <%= @commit.short_id -%> +<%= render partial: 'note_mr_or_commit_email', locals: { url: url } %> diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index b7758f191dc..0a650e3b2ca 100644 --- a/app/views/notify/note_merge_request_email.html.haml +++ b/app/views/notify/note_merge_request_email.html.haml @@ -1,5 +1,2 @@ %p.details - New comment for Merge Request - = @merge_request.to_reference - = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index d24e15af91f..e33d15daded 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -1,11 +1,4 @@ -New comment for Merge Request <%= @merge_request.to_reference %> +<% url = url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> -<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> - -<%= render 'simple_diff' if @discussion %> - -<% if current_application_settings.email_author_in_body %> - <%= @note.author_name %> wrote: -<% end %> - -<%= @note.note %> +New comment for Merge Request <%= @merge_request.to_reference -%> +<%= render partial: 'note_mr_or_commit_email', locals: { url: url }%> diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 9b60102947a..84784aaf2fd 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -76,15 +76,8 @@ module Gitlab @diff_lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a end - def highlighted_diff_lines(lines = self) - @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(lines, repository: self.repository).highlight - end - - def text_parsed_diff_lines(lines) - @text_parsed_diff_lines ||= - lines.map do | line | - "> " + line.text - end + def highlighted_diff_lines + @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight end # Array[] with right/left keys that contains Gitlab::Diff::Line objects which text is hightlighted diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index a80eb114c17..824b516f856 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -697,7 +697,7 @@ describe Notify do let(:note) { create(model, project: project, author: note_author) } it "includes diffs with character-level highlighting" do - is_expected.to have_body_text /\vars = {<\/span>/ + is_expected.to have_body_text /}<\/span><\/span>/ end it 'contains a link to the diff file' do @@ -743,9 +743,6 @@ describe Notify do subject { Notify.note_commit_email(recipient.id, note.id) } it_behaves_like 'a note email on a diff', :diff_note_on_commit - # it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - # let(:model) { commit } - # end it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'a user cannot unsubscribe through footer link' end @@ -757,9 +754,6 @@ describe Notify do subject { Notify.note_merge_request_email(recipient.id, note.id) } it_behaves_like 'a note email on a diff', :diff_note_on_merge_request - # it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - # let(:model) { merge_request } - # end it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' end diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index d4b1f480c56..187d0bc0150 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -595,7 +595,7 @@ describe Discussion, model: true do let(:truncated_lines) { subject.truncated_diff_lines } context "when diff is greater than allowed number of truncated diff lines " do - let(:initial_line_count) { subject.diff_file.diff_lines.count } + let(:initial_line_count) { subject.diff_lines.count } let(:truncated_line_count) { truncated_lines.count } it "returns fewer lines" do @@ -606,7 +606,7 @@ describe Discussion, model: true do end context "when some diff lines are meta" do - let(:initial_meta_lines?) { subject.diff_file.diff_lines.any?(&:meta?) } + let(:initial_meta_lines?) { subject.diff_lines.any?(&:meta?) } let(:truncated_meta_lines?) { truncated_lines.any?(&:meta?) } it "returns no meta lines" do -- cgit v1.2.1 From c0931722509bba7b26ce46777b6c71f976bc4b7b Mon Sep 17 00:00:00 2001 From: hhoopes Date: Fri, 2 Sep 2016 22:25:53 -0600 Subject: Clean up rubocop complaint --- spec/mailers/notify_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 824b516f856..76ea5f6be31 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -693,7 +693,7 @@ describe Notify do allow(Note).to receive(:find).with(note.id).and_return(note) end - shared_examples 'a note email on a diff' do | model | + shared_examples 'a note email on a diff' do |model| let(:note) { create(model, project: project, author: note_author) } it "includes diffs with character-level highlighting" do -- cgit v1.2.1 From 938de31167e262cb3eb952f6be1797b4c891d34c Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 14 Nov 2016 13:25:25 +0000 Subject: Fix CHANGELOG --- CHANGELOG.md | 1 - .../hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 56f749e94ac..549336e4dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -797,7 +797,6 @@ entry. ## 8.11.0 (2016-08-22) - Use test coverage value from the latest successful pipeline in badge. !5862 - - Add git diff context to notifications of new notes on merge requests !5855 (hoopes) - Add test coverage report badge. !5708 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Add Koding (online IDE) integration diff --git a/changelogs/unreleased/hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml b/changelogs/unreleased/hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml new file mode 100644 index 00000000000..73d8a52e001 --- /dev/null +++ b/changelogs/unreleased/hoopes-gitlab-ce-21027-add-diff-hunks-to-notification-emails.yml @@ -0,0 +1,4 @@ +--- +title: Add git diff context to notifications of new notes on merge requests +merge_request: +author: Heidi Hoopes -- cgit v1.2.1 From 260749e1648956479cf8a814862fcfa21f8f71fd Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 23 Nov 2016 15:54:35 +0000 Subject: Add `.find` poly --- app/assets/javascripts/extensions/array.js | 8 ----- app/assets/javascripts/extensions/array.js.es6 | 24 ++++++++++++++ spec/javascripts/extensions/array_spec.js | 23 ------------- spec/javascripts/extensions/array_spec.js.es6 | 46 ++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 31 deletions(-) delete mode 100644 app/assets/javascripts/extensions/array.js create mode 100644 app/assets/javascripts/extensions/array.js.es6 delete mode 100644 spec/javascripts/extensions/array_spec.js create mode 100644 spec/javascripts/extensions/array_spec.js.es6 diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js deleted file mode 100644 index fc6c130113d..00000000000 --- a/app/assets/javascripts/extensions/array.js +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */ -Array.prototype.first = function() { - return this[0]; -} - -Array.prototype.last = function() { - return this[this.length-1]; -} diff --git a/app/assets/javascripts/extensions/array.js.es6 b/app/assets/javascripts/extensions/array.js.es6 new file mode 100644 index 00000000000..717566a4715 --- /dev/null +++ b/app/assets/javascripts/extensions/array.js.es6 @@ -0,0 +1,24 @@ +/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */ +Array.prototype.first = function() { + return this[0]; +} + +Array.prototype.last = function() { + return this[this.length-1]; +} + +Array.prototype.find = Array.prototype.find || function(predicate, ...args) { + if (!this) throw new TypeError('Array.prototype.find called on null or undefined'); + if (typeof predicate !== 'function') throw new TypeError('predicate must be a function'); + + const list = Object(this); + const thisArg = args[1]; + let value = {}; + + for (let i = 0; i < list.length; i += 1) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) return value; + } + + return undefined; +}; diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js deleted file mode 100644 index c56e6c7789b..00000000000 --- a/spec/javascripts/extensions/array_spec.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable space-before-function-paren, no-var, padded-blocks */ - -/*= require extensions/array */ - -(function() { - describe('Array extensions', function() { - describe('first', function() { - return it('returns the first item', function() { - var arr; - arr = [0, 1, 2, 3, 4, 5]; - return expect(arr.first()).toBe(0); - }); - }); - return describe('last', function() { - return it('returns the last item', function() { - var arr; - arr = [0, 1, 2, 3, 4, 5]; - return expect(arr.last()).toBe(5); - }); - }); - }); - -}).call(this); diff --git a/spec/javascripts/extensions/array_spec.js.es6 b/spec/javascripts/extensions/array_spec.js.es6 new file mode 100644 index 00000000000..2ec759c8e80 --- /dev/null +++ b/spec/javascripts/extensions/array_spec.js.es6 @@ -0,0 +1,46 @@ +/* eslint-disable space-before-function-paren, no-var, padded-blocks */ + +/*= require extensions/array */ + +(function() { + describe('Array extensions', function() { + describe('first', function() { + return it('returns the first item', function() { + var arr; + arr = [0, 1, 2, 3, 4, 5]; + return expect(arr.first()).toBe(0); + }); + }); + describe('last', function() { + return it('returns the last item', function() { + var arr; + arr = [0, 1, 2, 3, 4, 5]; + return expect(arr.last()).toBe(5); + }); + }); + + describe('find', function () { + beforeEach(() => { + this.arr = [0, 1, 2, 3, 4, 5]; + }); + + it('returns the item that first passes the predicate function', () => { + expect(this.arr.find(item => item === 2)).toBe(2); + }); + + it('returns undefined if no items pass the predicate function', () => { + expect(this.arr.find(item => item === 6)).not.toBeDefined(); + }); + + it('error when called on undefined or null', () => { + expect(Array.prototype.find.bind(undefined, item => item === 1)).toThrow(); + expect(Array.prototype.find.bind(null, item => item === 1)).toThrow(); + }); + + it('error when predicate is not a function', () => { + expect(Array.prototype.find.bind(this.arr, 1)).toThrow(); + }); + }); + }); + +}).call(this); -- cgit v1.2.1 From 5981a0f365b9a312b87bfcebfb0d9365cb2f9c9a Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 25 Nov 2016 16:22:29 +0000 Subject: Update GitLab Workhorse to v1.0.1 v1.0.0 was mistakenly tagged with a lightweight, rather than annotated, tag, which caused compiled versions of workhorse to wrongly report their version. --- GITLAB_WORKHORSE_VERSION | 2 +- changelogs/unreleased/workhorse-v1-0-1.yml | 4 ++++ doc/install/installation.md | 2 +- doc/update/8.13-to-8.14.md | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/workhorse-v1-0-1.yml diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 3eefcb9dd5b..7dea76edb3d 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -1.0.0 +1.0.1 diff --git a/changelogs/unreleased/workhorse-v1-0-1.yml b/changelogs/unreleased/workhorse-v1-0-1.yml new file mode 100644 index 00000000000..c26c2d45b1d --- /dev/null +++ b/changelogs/unreleased/workhorse-v1-0-1.yml @@ -0,0 +1,4 @@ +--- +title: Update GitLab Workhorse to v1.0.1 +merge_request: 7759 +author: diff --git a/doc/install/installation.md b/doc/install/installation.md index dabd69d7466..ee02d6024d9 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -403,7 +403,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v1.0.0 + sudo -u git -H git checkout v1.0.1 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md index 46ea19d11d0..a0e895773ce 100644 --- a/doc/update/8.13-to-8.14.md +++ b/doc/update/8.13-to-8.14.md @@ -84,7 +84,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v1.0.0 +sudo -u git -H git checkout v1.0.1 sudo -u git -H make ``` -- cgit v1.2.1 From 117a078cbb22ab3890e519655fde2b2c314c6b46 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 25 Nov 2016 10:35:25 -0600 Subject: use standard gitlab namespace regex for group name validation --- app/views/shared/_group_form.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index fc1753ca082..0bc851b4256 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -13,8 +13,9 @@ .input-group-addon = root_url = f.text_field :path, placeholder: 'open-source', class: 'form-control', - autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_\\.]+", - required: true, title: 'Please choose a group name with no special characters.' + autofocus: local_assigns[:autofocus] || false, required: true, + pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, + title: 'Please choose a group name with no special characters.' - if @group.persisted? .alert.alert-warning.prepend-top-10 -- cgit v1.2.1 From 752d72f87635c1f43f3334d7e2dd96ab77eeba7d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 25 Nov 2016 17:38:51 +0100 Subject: Add docs for pipeline coverage [ci skip] --- doc/api/pipelines.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 6455c333faf..82351ae688f 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -41,7 +41,8 @@ Example of response "started_at": null, "finished_at": null, "committed_at": null, - "duration": null + "duration": null, + "coverage": "30.0" }, { "id": 48, @@ -64,7 +65,8 @@ Example of response "started_at": null, "finished_at": null, "committed_at": null, - "duration": null + "duration": null, + "coverage": null } ] ``` @@ -110,7 +112,8 @@ Example of response "started_at": null, "finished_at": "2016-08-11T11:32:35.145Z", "committed_at": null, - "duration": null + "duration": null, + "coverage": "30.0" } ``` @@ -155,7 +158,8 @@ Example of response "started_at": null, "finished_at": null, "committed_at": null, - "duration": null + "duration": null, + "coverage": null } ``` @@ -200,7 +204,8 @@ Response: "started_at": null, "finished_at": "2016-08-11T11:32:35.145Z", "committed_at": null, - "duration": null + "duration": null, + "coverage": null } ``` @@ -245,7 +250,8 @@ Response: "started_at": null, "finished_at": "2016-08-11T11:32:35.145Z", "committed_at": null, - "duration": null + "duration": null, + "coverage": null } ``` -- cgit v1.2.1 From 2ec4d167b68005ece13ed789c03f9816d90e8239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 15 Nov 2016 18:28:14 +0100 Subject: Refactor issuable description and metadata form sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/views/shared/issuable/_form.html.haml | 48 +-------- .../shared/issuable/form/_description.html.haml | 15 +++ app/views/shared/issuable/form/_metadata.html.haml | 38 +++++++ doc/development/limit_ee_conflicts.md | 111 +++++++++++++++------ 4 files changed, 138 insertions(+), 74 deletions(-) create mode 100644 app/views/shared/issuable/form/_description.html.haml create mode 100644 app/views/shared/issuable/form/_metadata.html.haml diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 9b9ad510444..3d515a05d46 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -16,20 +16,9 @@ = render 'shared/issuable/form/template_selector', issuable: issuable = render 'shared/issuable/form/title', issuable: issuable, form: form -.form-group.detail-page-description - = form.label :description, 'Description', class: 'control-label' - .col-sm-10 += render 'shared/issuable/form/description', issuable: issuable, form: form - = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: form, attr: :description, - classes: 'note-textarea', - placeholder: "Write a comment or drag your files here...", - supports_slash_commands: !issuable.persisted? - = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? - .clearfix - .error-alert - -- if issuable.is_a?(Issue) +- if issuable.respond_to?(:confidential) .form-group .col-sm-offset-2.col-sm-10 .checkbox @@ -37,38 +26,7 @@ = form.check_box :confidential This issue is confidential and should only be visible to team members with at least Reporter access. -- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - - has_due_date = issuable.has_attribute?(:due_date) - %hr - .row - %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } - .form-group.issue-assignee - = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" - .col-sm-10{ class: ("col-lg-8" if has_due_date) } - .issuable-form-select-holder - - if issuable.assignee_id - = form.hidden_field :assignee_id - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) - .form-group.issue-milestone - = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" - .col-sm-10{ class: ("col-lg-8" if has_due_date) } - .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" - .form-group - - has_labels = @labels && @labels.any? - = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" - = form.hidden_field :label_ids, multiple: true, value: '' - .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } - .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" - - if has_due_date - .col-lg-6 - .form-group - = form.label :due_date, "Due date", class: "control-label" - .col-sm-10 - .issuable-form-select-holder - = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" += render 'shared/issuable/form/metadata', issuable: issuable, form: form - if issuable.can_move?(current_user) %hr diff --git a/app/views/shared/issuable/form/_description.html.haml b/app/views/shared/issuable/form/_description.html.haml new file mode 100644 index 00000000000..dbace9ce401 --- /dev/null +++ b/app/views/shared/issuable/form/_description.html.haml @@ -0,0 +1,15 @@ +- issuable = local_assigns.fetch(:issuable) +- form = local_assigns.fetch(:form) + +.form-group.detail-page-description + = form.label :description, 'Description', class: 'control-label' + .col-sm-10 + + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do + = render 'projects/zen', f: form, attr: :description, + classes: 'note-textarea', + placeholder: "Write a comment or drag your files here...", + supports_slash_commands: !issuable.persisted? + = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? + .clearfix + .error-alert diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml new file mode 100644 index 00000000000..a47085230b8 --- /dev/null +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -0,0 +1,38 @@ +- issuable = local_assigns.fetch(:issuable) + +- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) + +- has_due_date = issuable.has_attribute?(:due_date) +- has_labels = @labels && @labels.any? +- form = local_assigns.fetch(:form) + +%hr +.row + %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } + .form-group.issue-assignee + = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" + .col-sm-10{ class: ("col-lg-8" if has_due_date) } + .issuable-form-select-holder + - if issuable.assignee_id + = form.hidden_field :assignee_id + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) + .form-group.issue-milestone + = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" + .col-sm-10{ class: ("col-lg-8" if has_due_date) } + .issuable-form-select-holder + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" + .form-group + - has_labels = @labels && @labels.any? + = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" + = form.hidden_field :label_ids, multiple: true, value: '' + .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } + .issuable-form-select-holder + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" + - if has_due_date + .col-lg-6 + .form-group + = form.label :due_date, "Due date", class: "control-label" + .col-sm-10 + .issuable-form-select-holder + = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" diff --git a/doc/development/limit_ee_conflicts.md b/doc/development/limit_ee_conflicts.md index b7e6387838e..568dedf1669 100644 --- a/doc/development/limit_ee_conflicts.md +++ b/doc/development/limit_ee_conflicts.md @@ -143,109 +143,162 @@ to resolve when you add the indentation to the equation. For instance this kind of thing: ```haml +.form-group.detail-page-description + = form.label :description, 'Description', class: 'control-label' + .col-sm-10 + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do + = render 'projects/zen', f: form, attr: :description, + classes: 'note-textarea', + placeholder: "Write a comment or drag your files here...", + supports_slash_commands: !issuable.persisted? + = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? + .clearfix + .error-alert +- if issuable.is_a?(Issue) + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = form.label :confidential do + = form.check_box :confidential + This issue is confidential and should only be visible to team members with at least Reporter access. - if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - has_due_date = issuable.has_attribute?(:due_date) %hr .row %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } .form-group.issue-assignee - = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" + = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - if issuable.assignee_id - = f.hidden_field :assignee_id + = form.hidden_field :assignee_id = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) .form-group.issue-milestone - = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" + = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = @labels && @labels.any? - = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" - = f.hidden_field :label_ids, multiple: true, value: '' + = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" + = form.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label" - + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label" - if issuable.respond_to?(:weight) + - weight_options = Issue.weight_options + - weight_options.delete(Issue::WEIGHT_ALL) + - weight_options.delete(Issue::WEIGHT_ANY) .form-group - = f.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do + = form.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do Weight .col-sm-10{ class: ("col-lg-8" if has_due_date) } - = f.select :weight, issues_weight_options(issuable.weight, edit: true), { include_blank: true }, - { class: 'select2 js-select2', data: { placeholder: "Select weight" }} - + .issuable-form-select-holder + - if issuable.weight + = form.hidden_field :weight + = dropdown_tag(issuable.weight || "Weight", options: { title: "Select weight", toggle_class: 'js-weight-select js-issuable-form-weight', dropdown_class: "dropdown-menu-selectable dropdown-menu-weight", + placeholder: "Search weight", data: { field_name: "#{issuable.class.model_name.param_key}[weight]" , default_label: "Weight" } }) do + %ul + - weight_options.each do |weight| + %li + %a{href: "#", data: { id: weight, none: weight === Issue::WEIGHT_NONE }, class: ("is-active" if issuable.weight == weight)} + = weight - if has_due_date .col-lg-6 .form-group - = f.label :due_date, "Due date", class: "control-label" + = form.label :due_date, "Due date", class: "control-label" .col-sm-10 .issuable-form-select-holder - = f.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" + = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" ``` could be simplified by using partials: ```haml -= render 'metadata_form', issuable: issuable += render 'shared/issuable/form/description', issuable: issuable, form: form + +- if issuable.respond_to?(:confidential) + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = form.label :confidential do + = form.check_box :confidential + This issue is confidential and should only be visible to team members with at least Reporter access. + += render 'shared/issuable/form/metadata', issuable: issuable, form: form ``` -and then the `_metadata_form.html.haml` could be as follows: +and then the `app/views/shared/issuable/form/_metadata.html.haml` could be as follows: ```haml +- issuable = local_assigns.fetch(:issuable) + - return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - has_due_date = issuable.has_attribute?(:due_date) +- has_labels = @labels && @labels.any? +- form = local_assigns.fetch(:form) + %hr .row %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } .form-group.issue-assignee - = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" + = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - if issuable.assignee_id - = f.hidden_field :assignee_id + = form.hidden_field :assignee_id = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) .form-group.issue-milestone - = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" + = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = @labels && @labels.any? - = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" - = f.hidden_field :label_ids, multiple: true, value: '' + = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" + = form.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label" + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label" - = render 'weight_form', issuable: issuable, has_due_date: has_due_date + = render "shared/issuable/form/weight", issuable: issuable, form: form - if has_due_date .col-lg-6 .form-group - = f.label :due_date, "Due date", class: "control-label" + = form.label :due_date, "Due date", class: "control-label" .col-sm-10 .issuable-form-select-holder - = f.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" + = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" ``` -and then the `_weight_form.html.haml` could be as follows: +and then the `app/views/shared/issuable/form/_weight.html.haml` could be as follows: ```haml +- issuable = local_assigns.fetch(:issuable) + - return unless issuable.respond_to?(:weight) - has_due_date = issuable.has_attribute?(:due_date) +- form = local_assigns.fetch(:form) .form-group - = f.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do + = form.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do Weight .col-sm-10{ class: ("col-lg-8" if has_due_date) } - = f.select :weight, issues_weight_options(issuable.weight, edit: true), { include_blank: true }, - { class: 'select2 js-select2', data: { placeholder: "Select weight" }} + .issuable-form-select-holder + - if issuable.weight + = form.hidden_field :weight + + = weight_dropdown_tag(issuable, toggle_class: 'js-issuable-form-weight') do + %ul + - Issue.weight_options.each do |weight| + %li + %a{ href: '#', data: { id: weight, none: weight === Issue::WEIGHT_NONE }, class: ("is-active" if issuable.weight == weight) } + = weight ``` Note: -- cgit v1.2.1 From d8e21b1bfda544033e4b1830fae7b6f9101279a1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 25 Nov 2016 17:00:47 +0000 Subject: Adds spinner class --- app/assets/javascripts/environments/components/environment.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 35e183a9086..84faabf938a 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -181,7 +181,7 @@
- +
Date: Fri, 25 Nov 2016 18:26:24 +0100 Subject: Check that both '/help' and '/help/' URLs have the same behaviour The links in the help page may be modified. This new test checks that URLs in this page are absolute and do not depend on the presence of a trailing slash in the URL. Signed-off-by: David Wagner --- spec/features/help_pages_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index e2101b333e2..73d03837144 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -10,4 +10,28 @@ describe 'Help Pages', feature: true do expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"") end end + + describe 'Get the main help page' do + shared_examples_for 'help page' do + it 'prefixes links correctly' do + expect(page).to have_selector('div.documentation-index > ul a[href="/help/api/README.md"]') + end + end + + context 'without a trailing slash' do + before do + visit help_path + end + + it_behaves_like 'help page' + end + + context 'with a trailing slash' do + before do + visit help_path + '/' + end + + it_behaves_like 'help page' + end + end end -- cgit v1.2.1 From a1f53e92caa8a95889c3fee683f8d0b2590228ac Mon Sep 17 00:00:00 2001 From: awhildy Date: Fri, 25 Nov 2016 20:19:29 -0800 Subject: [ci skip] UX Guide: add guidance for max height for dropdowns Fix spelling --- doc/development/ux_guide/components.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 764c3355714..57e5d03d608 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -91,7 +91,9 @@ TODO: Will update this section when the new filters UI is implemented. ![Dropdown states](img/components-dropdown.png) +### Max size +The max height for dropdowns should target **10-15 items**. If the height of the dropdown is too large, the list becomes very hard to parse and it is easy to visually lose track of the item you are looking for. Usability also suffers as more mouse movement is required, and you have a larger area in which you hijack the scroll away from the page level. While it may initially seem counterintuitive to not show as many items as you can, it is actually quicker and easier to process the information when it is cropped at a reasonable height. --- -- cgit v1.2.1 From b72bd838d76015fb9ef0ab4d8be57a9c8130b744 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Sat, 26 Nov 2016 07:11:05 +0100 Subject: Backporting little groups_helper refactor from gitlab-org/gitlab-ee!904 --- app/helpers/groups_helper.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 75cd9eece5c..19ab059aea6 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -8,11 +8,7 @@ module GroupsHelper group = Group.find_by(path: group) end - if group && group.avatar.present? - group.avatar.url - else - image_path('no_group_avatar.png') - end + group.try(:avatar_url) || image_path('no_group_avatar.png') end def group_title(group, name = nil, url = nil) -- cgit v1.2.1 From 7bd214091dbbbdff46b39e2b4505c64d931fa421 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sat, 26 Nov 2016 14:36:00 +0500 Subject: Remove unnecessary require_relative calls from finder --- app/finders/issuable_finder.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 6297b2db369..a48f22cee07 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -16,8 +16,6 @@ # label_name: string # sort: string # -require_relative 'projects_finder' - class IssuableFinder NONE = '0' -- cgit v1.2.1 From c47d8ab69e108ef0cb30463fc2f02fbd1d03409b Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sat, 19 Nov 2016 15:40:41 +0000 Subject: Removed leave buttons from settings dropdowns Updated specs --- app/views/layouts/nav/_group_settings.html.haml | 12 ++-------- app/views/layouts/nav/_project.html.haml | 17 ++++---------- .../members/_access_request_buttons.html.haml | 26 +++++++++++++--------- ...e-project-and-leave-group-should-be-buttons.yml | 5 +++++ .../members/last_owner_cannot_leave_group_spec.rb | 4 ++-- .../groups/members/member_leaves_group_spec.rb | 2 +- .../groups/members/user_requests_access_spec.rb | 2 +- ...group_member_cannot_leave_group_project_spec.rb | 2 +- ...uester_cannot_request_access_to_project_spec.rb | 2 +- .../projects/members/member_leaves_project_spec.rb | 2 +- .../members/owner_cannot_leave_project_spec.rb | 4 ++-- 11 files changed, 36 insertions(+), 42 deletions(-) create mode 100644 changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index c0328fe8842..1579d8f1662 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,10 +1,8 @@ - if current_user - can_admin_group = can?(current_user, :admin_group, @group) - can_edit = can?(current_user, :admin_group, @group) - - member = @group.members.find_by(user_id: current_user.id) - - can_leave = member && can?(current_user, :destroy_group_member, member) - - if can_admin_group || can_edit || can_leave + - if can_admin_group || can_edit .controls .dropdown.group-settings-dropdown %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} @@ -14,13 +12,7 @@ - if can_admin_group = nav_link(path: 'groups#projects') do = link_to 'Projects', projects_group_path(@group), title: 'Projects' - - if (can_edit || can_leave) && can_admin_group + - if can_edit && can_admin_group %li.divider - - if can_edit %li = link_to 'Edit Group', edit_group_path(@group) - - if can_leave - %li - = link_to polymorphic_path([:leave, @group, :members]), - data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do - Leave Group diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 99a58bbb676..cc24e51b268 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -6,23 +6,14 @@ = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - can_edit = can?(current_user, :admin_project, @project) - -# We don't use @project.team.find_member because it searches for group members too... - - member = @project.members.find_by(user_id: current_user.id) - - can_leave = member && can?(current_user, :destroy_project_member, member) = render 'layouts/nav/project_settings', can_edit: can_edit - - if can_edit || can_leave + - if can_edit %li.divider - - if can_edit - %li - = link_to edit_project_path(@project) do - Edit Project - - if can_leave - %li - = link_to polymorphic_path([:leave, @project, :members]), - data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do - Leave Project + %li + = link_to edit_project_path(@project) do + Edit Project .scrolling-tabs-container{ class: nav_control_class } .fade-left diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index eff914398bb..e166dfab710 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,10 +1,16 @@ -- if can?(current_user, :request_access, source) - - if requester = source.requesters.find_by(user_id: current_user.id) - = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: remove_member_message(requester) }, - class: 'btn' - - else - = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), - method: :post, - class: 'btn' +- model_name = source.model_name.to_s.downcase + +- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) + = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(source) }, + class: 'btn' +- elsif requester = source.requesters.find_by(user_id: current_user.id) + = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: remove_member_message(requester) }, + class: 'btn' +- elsif source.request_access_enabled && can?(current_user, :request_access, source) + = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), + method: :post, + class: 'btn' diff --git a/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml b/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml new file mode 100644 index 00000000000..99dbe4a32a0 --- /dev/null +++ b/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml @@ -0,0 +1,5 @@ +--- +title: Moved Leave Project and Leave Group buttons to access_request_buttons from + the settings dropdown +merge_request: 7600 +author: diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb index 33bf6d3752f..be60b0489c7 100644 --- a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb +++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb @@ -10,7 +10,7 @@ feature 'Groups > Members > Last owner cannot leave group', feature: true do visit group_path(group) end - scenario 'user does not see a "Leave Group" link' do - expect(page).not_to have_content 'Leave Group' + scenario 'user does not see a "Leave group" link' do + expect(page).not_to have_content 'Leave group' end end diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb index 3185ff924b9..ac4d94658ae 100644 --- a/spec/features/groups/members/member_leaves_group_spec.rb +++ b/spec/features/groups/members/member_leaves_group_spec.rb @@ -13,7 +13,7 @@ feature 'Groups > Members > Member leaves group', feature: true do end scenario 'user leaves group' do - click_link 'Leave Group' + click_link 'Leave group' expect(current_path).to eq(dashboard_groups_path) expect(group.users.exists?(user.id)).to be_falsey diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb index d8c9c487996..e4b5ea91bd3 100644 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ b/spec/features/groups/members/user_requests_access_spec.rb @@ -29,7 +29,7 @@ feature 'Groups > Members > User requests access', feature: true do expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' - expect(page).not_to have_content 'Leave Group' + expect(page).not_to have_content 'Leave group' end scenario 'user does not see private projects' do diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb index 728c0e16361..b483ba4c54c 100644 --- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb @@ -12,6 +12,6 @@ feature 'Projects > Members > Group member cannot leave group project', feature: end scenario 'user does not see a "Leave project" link' do - expect(page).not_to have_content 'Leave Project' + expect(page).not_to have_content 'Leave project' end end diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb index 4973e0aee85..bdeeef57273 100644 --- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb +++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Projects > Members > Group requester cannot request access to project', feature: true do +feature 'Projects > Members > Group requester cannot request access to project', feature: true, js: true do let(:user) { create(:user) } let(:owner) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 79dec442818..5daa932e4e6 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -11,7 +11,7 @@ feature 'Projects > Members > Member leaves project', feature: true do end scenario 'user leaves project' do - click_link 'Leave Project' + click_link 'Leave project' expect(current_path).to eq(dashboard_projects_path) expect(project.users.exists?(user.id)).to be_falsey diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb index 6e948b7a616..b26d55c5d5d 100644 --- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb @@ -8,7 +8,7 @@ feature 'Projects > Members > Owner cannot leave project', feature: true do visit namespace_project_path(project.namespace, project) end - scenario 'user does not see a "Leave Project" link' do - expect(page).not_to have_content 'Leave Project' + scenario 'user does not see a "Leave project" link' do + expect(page).not_to have_content 'Leave project' end end -- cgit v1.2.1 From 34c550b054c77699d795cec1daf90b23b2fb7ba5 Mon Sep 17 00:00:00 2001 From: Ryan Harris Date: Sat, 26 Nov 2016 16:14:02 -0500 Subject: Fix the width of project avatars in order to adjust alignment within their container element --- app/assets/stylesheets/framework/avatar.scss | 1 + changelogs/unreleased/24999-fix-project-avatar-alignment.yml | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 changelogs/unreleased/24999-fix-project-avatar-alignment.yml diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index ad0d387067f..c0dd1cb3667 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -80,6 +80,7 @@ border-radius: 0; border: none; height: auto; + width: 100%; margin: 0; align-self: center; } diff --git a/changelogs/unreleased/24999-fix-project-avatar-alignment.yml b/changelogs/unreleased/24999-fix-project-avatar-alignment.yml new file mode 100644 index 00000000000..7af812e7359 --- /dev/null +++ b/changelogs/unreleased/24999-fix-project-avatar-alignment.yml @@ -0,0 +1,4 @@ +--- +title: Adjust the width of project avatars to fix alignment within their container +merge_request: +author: Ryan Harris -- cgit v1.2.1 From 084d90acc4a58fbbb97fb524ada947e06d604202 Mon Sep 17 00:00:00 2001 From: Ryan Harris Date: Sat, 26 Nov 2016 23:34:19 -0500 Subject: Changes project dashboard tabs to sentence casing --- app/views/dashboard/_projects_head.html.haml | 6 +++--- changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index f7abad54286..48b0fd504f4 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -4,13 +4,13 @@ %ul.nav-links = nav_link(page: [dashboard_projects_path, root_path]) do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do - Your Projects + Your projects = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do - Starred Projects + Starred projects = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do - Explore Projects + Explore projects .nav-controls = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| diff --git a/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml b/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml new file mode 100644 index 00000000000..cc8b0e28277 --- /dev/null +++ b/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml @@ -0,0 +1,4 @@ +--- +title: Sentence cased the nav tab headers on the project dashboard page +merge_request: +author: Ryan Harris -- cgit v1.2.1 From 5a5e03b5aa01c837ae4e1fcc02f7f9def960e98b Mon Sep 17 00:00:00 2001 From: awhildy Date: Mon, 21 Nov 2016 13:30:55 -0800 Subject: [ci skip] UX Guide: Anchor hover guidance include color change Primary and secondary links should be dark blue on hover Update anchor image to dark blue for secondary Clean up markdown Fix anchorlinks image --- doc/development/ux_guide/components.md | 18 +++++------------- .../ux_guide/img/components-anchorlinks.png | Bin 19948 -> 30089 bytes 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 57e5d03d608..8e51edd23ef 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -43,7 +43,7 @@ Primary links are blue in their rest state. Secondary links (such as the time st #### Hover -An underline should always be added on hover. A gray link becomes blue on hover. +On hover, an underline should be added and the color should change. Both the primary and secondary link should become the darker blue color on hover. #### Focus @@ -72,9 +72,7 @@ Secondary buttons are for alternative commands. They should be conveyed by a bu ### Icon and text treatment Text should be in sentence case, where only the first word is capitalized. "Create issue" is correct, not "Create Issue". Buttons should only contain an icon or a text, not both. ->>> -TODO: Rationalize this. Ensure that we still believe this. ->>> +> TODO: Rationalize this. Ensure that we still believe this. ### Colors Follow the color guidance on the [basics](basics.md#color) page. The default color treatment is the white/grey button. @@ -85,9 +83,7 @@ Follow the color guidance on the [basics](basics.md#color) page. The default col Dropdowns are used to allow users to choose one (or many) options from a list of options. If this list of options is more 20, there should generally be a way to search through and filter the options (see the complex filter dropdowns below.) ->>> -TODO: Will update this section when the new filters UI is implemented. ->>> +> TODO: Will update this section when the new filters UI is implemented. ![Dropdown states](img/components-dropdown.png) @@ -166,9 +162,7 @@ Cover blocks are generally used to create a heading element for a page, such as ## Panels ->>> -TODO: Catalog how we are currently using panels and rationalize how they relate to alerts ->>> +> TODO: Catalog how we are currently using panels and rationalize how they relate to alerts ![Panels](img/components-panels.png) @@ -176,9 +170,7 @@ TODO: Catalog how we are currently using panels and rationalize how they relate ## Alerts ->>> -TODO: Catalog how we are currently using alerts ->>> +> TODO: Catalog how we are currently using alerts ![Alerts](img/components-alerts.png) diff --git a/doc/development/ux_guide/img/components-anchorlinks.png b/doc/development/ux_guide/img/components-anchorlinks.png index 7dd6a8a3876..4a9c730566c 100644 Binary files a/doc/development/ux_guide/img/components-anchorlinks.png and b/doc/development/ux_guide/img/components-anchorlinks.png differ -- cgit v1.2.1 From 3761a0c50ea13b86152417a5e659b30879cb16b1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 27 Nov 2016 16:24:43 +0100 Subject: Extend pipelines factory with transient config attribute --- spec/factories/ci/pipelines.rb | 21 ++++++++------------- spec/services/ci/process_pipeline_service_spec.rb | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 23585db6ebd..1735791f644 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -21,21 +21,16 @@ FactoryGirl.define do end factory :ci_pipeline do - after(:build) do |pipeline| - allow(pipeline).to receive(:ci_yaml_file) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - end - end - end - - factory(:ci_pipeline_with_yaml) do - transient { yaml nil } + transient { config nil } after(:build) do |pipeline, evaluator| - raise ArgumentError unless evaluator.yaml - - allow(pipeline).to receive(:ci_yaml_file) - .and_return(YAML.dump(evaluator.yaml)) + allow(pipeline).to receive(:ci_yaml_file) do + if evaluator.config + YAML.dump(evaluator.config) + else + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end + end end end end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 306943a5488..ebb11166964 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -295,7 +295,7 @@ describe Ci::ProcessPipelineService, services: true do context 'when there are builds that are not created yet' do let(:pipeline) do - create(:ci_pipeline_with_yaml, yaml: config) + create(:ci_pipeline, config: config) end let(:config) do -- cgit v1.2.1 From 1e66f35c560b5a0067851cabb792fe23281cfd51 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 28 Nov 2016 16:50:08 +0800 Subject: Pass `--load-images=no` to PhantomJS via Capybara/Poltergeist We were unintentionally hitting `gravatar.com` whenever a test that used Poltergeist was run. This was certainly wasting their resources and slowing down our tests even further, for no reason. --- features/support/capybara.rb | 10 +++++++++- spec/support/capybara.rb | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/features/support/capybara.rb b/features/support/capybara.rb index dae0d0f918c..47372df152d 100644 --- a/features/support/capybara.rb +++ b/features/support/capybara.rb @@ -6,7 +6,15 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 15 Capybara.javascript_driver = :poltergeist Capybara.register_driver :poltergeist do |app| - Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768]) + Capybara::Poltergeist::Driver.new( + app, + js_errors: true, + timeout: timeout, + window_size: [1366, 768], + phantomjs_options: [ + '--load-images=no' + ] + ) end Capybara.default_max_wait_time = timeout diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index e1f90e17cce..16d5f2bf0b8 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -7,7 +7,15 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10 Capybara.javascript_driver = :poltergeist Capybara.register_driver :poltergeist do |app| - Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768]) + Capybara::Poltergeist::Driver.new( + app, + js_errors: true, + timeout: timeout, + window_size: [1366, 768], + phantomjs_options: [ + '--load-images=no' + ] + ) end Capybara.default_max_wait_time = timeout -- cgit v1.2.1 From 13ad9a745a392e0bf0cedd0e1f318c1acee9b969 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 28 Nov 2016 13:08:14 +0800 Subject: Speed up Project security access specs Prior, every single test was creating four `ProjectMember` objects, each of which created one `User` record, even though each test only used _one_ of those Users, if any. Now each test only creates the single user record it needs, if it needs one. This shaves minutes off of each spec file changed here. --- .../security/project/internal_access_spec.rb | 517 ++++++++++----------- .../security/project/private_access_spec.rb | 445 +++++++++--------- .../security/project/public_access_spec.rb | 515 ++++++++++---------- spec/support/matchers/access_matchers.rb | 35 +- 4 files changed, 747 insertions(+), 765 deletions(-) diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index b6acc509342..1897c8119d2 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -5,19 +5,6 @@ describe "Internal Project Access", feature: true do let(:project) { create(:project, :internal) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end - describe "Project should be internal" do describe '#internal?' do subject { project.internal? } @@ -28,213 +15,213 @@ describe "Internal Project Access", feature: true do describe "GET /:project_path" do subject { namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tree/master" do subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commits/master" do subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commit/:sha" do subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/compare" do subject { namespace_project_compare_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/project_members" do subject { namespace_project_project_members_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/edit" do subject { edit_namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/deploy_keys" do subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues" do subject { namespace_project_issues_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } subject { edit_namespace_project_issue_path(project.namespace, project, issue) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests" do subject { namespace_project_merge_requests_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests/new" do subject { new_namespace_project_merge_request_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/branches" do @@ -245,15 +232,15 @@ describe "Internal Project Access", feature: true do allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tags" do @@ -264,58 +251,58 @@ describe "Internal Project Access", feature: true do allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/hooks" do subject { namespace_project_hooks_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/pipelines" do subject { namespace_project_pipelines_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/builds" do @@ -324,29 +311,29 @@ describe "Internal Project Access", feature: true do context "when allowed for public and internal" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "when disallowed for public and internal" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -358,73 +345,73 @@ describe "Internal Project Access", feature: true do context "when allowed for public and internal" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "when disallowed for public and internal" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end describe "GET /:project_path/environments" do subject { namespace_project_environments_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/:id" do let(:environment) { create(:environment, project: project) } subject { namespace_project_environment_path(project.namespace, project, environment) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/new" do subject { new_namespace_project_environment_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/container_registry" do @@ -435,14 +422,14 @@ describe "Internal Project Access", feature: true do subject { namespace_project_container_registry_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 79417c769a8..290ddb4c6dd 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -5,19 +5,6 @@ describe "Private Project Access", feature: true do let(:project) { create(:project, :private) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end - describe "Project should be private" do describe '#private?' do subject { project.private? } @@ -28,185 +15,185 @@ describe "Private Project Access", feature: true do describe "GET /:project_path" do subject { namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tree/master" do subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commits/master" do subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/commit/:sha" do subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/compare" do subject { namespace_project_compare_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/project_members" do subject { namespace_project_project_members_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))} - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/edit" do subject { edit_namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/deploy_keys" do subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues" do subject { namespace_project_issues_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } subject { edit_namespace_project_issue_path(project.namespace, project, issue) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests" do subject { namespace_project_merge_requests_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/branches" do @@ -217,15 +204,15 @@ describe "Private Project Access", feature: true do allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/tags" do @@ -236,72 +223,72 @@ describe "Private Project Access", feature: true do allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/hooks" do subject { namespace_project_hooks_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/pipelines" do subject { namespace_project_pipelines_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/builds" do subject { namespace_project_builds_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/builds/:id" do @@ -309,58 +296,58 @@ describe "Private Project Access", feature: true do let(:build) { create(:ci_build, pipeline: pipeline) } subject { namespace_project_build_path(project.namespace, project, build.id) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments" do subject { namespace_project_environments_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/:id" do let(:environment) { create(:environment, project: project) } subject { namespace_project_environment_path(project.namespace, project, environment) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/new" do subject { new_namespace_project_environment_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/container_registry" do @@ -371,14 +358,14 @@ describe "Private Project Access", feature: true do subject { namespace_project_container_registry_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 985663e7c98..bed9e92fcb6 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -5,19 +5,6 @@ describe "Public Project Access", feature: true do let(:project) { create(:project, :public) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end - describe "Project should be public" do describe '#public?' do subject { project.public? } @@ -28,114 +15,114 @@ describe "Public Project Access", feature: true do describe "GET /:project_path" do subject { namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/tree/master" do subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/commits/master" do subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/commit/:sha" do subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/compare" do subject { namespace_project_compare_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/project_members" do subject { namespace_project_project_members_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } - it { is_expected.to be_allowed_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:visitor) } + it { is_expected.to be_allowed_for(:external) } end describe "GET /:project_path/pipelines" do subject { namespace_project_pipelines_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } subject { namespace_project_pipeline_path(project.namespace, project, pipeline) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/builds" do @@ -144,29 +131,29 @@ describe "Public Project Access", feature: true do context "when allowed for public" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "when disallowed for public" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -178,73 +165,73 @@ describe "Public Project Access", feature: true do context "when allowed for public" do before { project.update(public_builds: true) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "when disallowed for public" do before { project.update(public_builds: false) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end describe "GET /:project_path/environments" do subject { namespace_project_environments_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/:id" do let(:environment) { create(:environment, project: project) } subject { namespace_project_environment_path(project.namespace, project, environment) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/environments/new" do subject { new_namespace_project_environment_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/blob" do @@ -252,127 +239,127 @@ describe "Public Project Access", feature: true do subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/edit" do subject { edit_namespace_project_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/deploy_keys" do subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/issues" do subject { namespace_project_issues_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } subject { edit_namespace_project_issue_path(project.namespace, project, issue) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/merge_requests" do subject { namespace_project_merge_requests_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/merge_requests/new" do subject { new_namespace_project_merge_request_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/branches" do @@ -383,15 +370,15 @@ describe "Public Project Access", feature: true do allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/tags" do @@ -402,29 +389,29 @@ describe "Public Project Access", feature: true do allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/hooks" do subject { namespace_project_hooks_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_denied_for(:developer).of(project) } + it { is_expected.to be_denied_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/container_registry" do @@ -435,14 +422,14 @@ describe "Public Project Access", feature: true do subject { namespace_project_container_registry_index_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end end diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb index 0497e391860..691d7e05f57 100644 --- a/spec/support/matchers/access_matchers.rb +++ b/spec/support/matchers/access_matchers.rb @@ -7,7 +7,7 @@ module AccessMatchers extend RSpec::Matchers::DSL include Warden::Test::Helpers - def emulate_user(user) + def emulate_user(user, project = nil) case user when :user login_as(create(:user)) @@ -18,6 +18,18 @@ module AccessMatchers when :external login_as(create(:user, external: true)) when User + login_as(user) + when :owner + raise ArgumentError, "cannot emulate owner without project" unless project + + login_as(project.owner) + when *Gitlab::Access.sym_options.keys + raise ArgumentError, "cannot emulate user #{user} without project" unless project + + role = user + user = create(:user) + project.public_send(:"add_#{role}", user) + login_as(user) else raise ArgumentError, "cannot emulate user #{user}" @@ -26,8 +38,7 @@ module AccessMatchers def description_for(user, type) if user.kind_of?(User) - # User#inspect displays too much information for RSpec's description - # messages + # User#inspect displays too much information for RSpec's descriptions "be #{type} for the specified user" else "be #{type} for #{user}" @@ -36,21 +47,31 @@ module AccessMatchers matcher :be_allowed_for do |user| match do |url| - emulate_user(user) - visit url + emulate_user(user, @project) + visit(url) + status_code != 404 && current_path != new_user_session_path end + chain :of do |project| + @project = project + end + description { description_for(user, 'allowed') } end matcher :be_denied_for do |user| match do |url| - emulate_user(user) - visit url + emulate_user(user, @project) + visit(url) + status_code == 404 || current_path == new_user_session_path end + chain :of do |project| + @project = project + end + description { description_for(user, 'denied') } end end -- cgit v1.2.1 From a4e41107005c538e0d6be342a2ba626f25816d37 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sun, 27 Nov 2016 22:35:44 +0500 Subject: Refactor issuable_filters_present to reduce duplications https://gitlab.com/gitlab-org/gitlab-ce/issues/23546 --- app/helpers/issuables_helper.rb | 20 +++++++++++++++----- app/views/shared/issuable/_filter.html.haml | 4 ++-- .../issuable_filters_present-refactor.yml | 4 ++++ spec/helpers/issuables_helper_spec.rb | 21 +++++++++++++++++++++ 4 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/issuable_filters_present-refactor.yml diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8bebda07787..6584aa3edd5 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -143,6 +143,20 @@ module IssuablesHelper end end + def issuable_filter_params + [ + :search, + :author_id, + :assignee_id, + :milestone_title, + :label_name + ] + end + + def issuable_filter_present? + issuable_filter_params.any? { |k| params.key?(k) } + end + private def assigned_issuables_count(assignee, issuable_type, state) @@ -165,13 +179,9 @@ module IssuablesHelper end end - def issuable_filters_present - params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name] - end - def issuables_count_for_state(issuable_type, state) issuables_finder = public_send("#{issuable_type}_finder") - + params = issuables_finder.params.merge(state: state) finder = issuables_finder.class.new(issuables_finder.current_user, params) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index b7e5e928993..e3503981afe 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -29,9 +29,9 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - - if issuable_filters_present + - if issuable_filter_present? .filter-item.inline.reset-filters - %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters + %a{href: page_filter_path(without: issuable_filter_params)} Reset filters .pull-right - if boards_page diff --git a/changelogs/unreleased/issuable_filters_present-refactor.yml b/changelogs/unreleased/issuable_filters_present-refactor.yml new file mode 100644 index 00000000000..c131f9cb68e --- /dev/null +++ b/changelogs/unreleased/issuable_filters_present-refactor.yml @@ -0,0 +1,4 @@ +--- +title: Refactor issuable_filters_present to reduce duplications +merge_request: 7776 +author: Semyon Pupkov diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 62cc10f579a..a4f08dc4af0 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -114,4 +114,25 @@ describe IssuablesHelper do end end end + + describe '#issuable_filter_present?' do + it 'returns true when any key is present' do + allow(helper).to receive(:params).and_return( + ActionController::Parameters.new(milestone_title: 'Velit consectetur asperiores natus delectus.', + project_id: 'gitlabhq', + scope: 'all') + ) + + expect(helper.issuable_filter_present?).to be_truthy + end + + it 'returns false when no key is present' do + allow(helper).to receive(:params).and_return( + ActionController::Parameters.new(project_id: 'gitlabhq', + scope: 'all') + ) + + expect(helper.issuable_filter_present?).to be_falsey + end + end end -- cgit v1.2.1 From d9a2093e7e6b0d532131b18dc57c240f5b4a7c55 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Mon, 28 Nov 2016 12:03:53 +0100 Subject: Prevent error when submitting a merge request and pipeline is not defined --- .../projects/merge_requests_controller.rb | 2 +- app/models/merge_request.rb | 2 +- .../projects/merge_requests/_new_submit.html.haml | 8 +++--- app/views/projects/merge_requests/_show.html.haml | 5 ++-- ...rror-undefined-method-size-for-nil-nilclass.yml | 4 +++ .../merge_requests/_new_submit.html.haml_spec.rb | 31 ++++++++++++++++++++++ 6 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml create mode 100644 spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e24a670631f..a2225cc8343 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -564,7 +564,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_pipelines_vars @pipelines = @merge_request.all_pipelines @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline.present? + @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 end def define_new_vars diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 69c6aa700d6..38d8c15e6b0 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -781,7 +781,7 @@ class MergeRequest < ActiveRecord::Base end def all_pipelines - return unless source_project + return Ci::Pipeline.none unless source_project @all_pipelines ||= source_project.pipelines .where(sha: all_commits_sha, ref: source_branch) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 9c6f562f7db..4a08ed045f4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -34,10 +34,11 @@ = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do Pipelines %span.badge= @pipelines.size + - if @pipeline.present? %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds - %span.badge= @statuses.size + %span.badge= @statuses_count %li.diffs-tab = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do Changes @@ -48,9 +49,10 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - # This tab is always loaded via AJAX - - if @pipelines.any? + - if @pipeline.present? #builds.builds.tab-pane = render "projects/merge_requests/show/builds" + - if @pipelines.any? #pipelines.pipelines.tab-pane = render "projects/merge_requests/show/pipelines" @@ -65,5 +67,5 @@ :javascript var merge_request = new MergeRequest({ action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", - buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}" + buildsLoaded: "#{@pipeline.present? ? 'true' : 'false'}" }); diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index a497f418c7c..0e2975bd551 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -59,15 +59,16 @@ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do Commits %span.badge= @commits_count - - if @pipeline + - if @pipelines.any? %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines %span.badge= @pipelines.size + - if @pipeline.present? %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do Builds - %span.badge= @statuses.size + %span.badge= @statuses_count %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do Changes diff --git a/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml b/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml new file mode 100644 index 00000000000..4b4aea79380 --- /dev/null +++ b/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml @@ -0,0 +1,4 @@ +--- +title: Prevent error when submitting a merge request and pipeline is not defined +merge_request: 7707 +author: diff --git a/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb new file mode 100644 index 00000000000..4f698a34ab5 --- /dev/null +++ b/spec/views/projects/merge_requests/_new_submit.html.haml_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe 'projects/merge_requests/_new_submit.html.haml', :view do + let(:merge_request) { create(:merge_request) } + let!(:pipeline) { create(:ci_empty_pipeline) } + + before do + controller.prepend_view_path('app/views/projects') + + assign(:merge_request, merge_request) + assign(:commits, merge_request.commits) + assign(:project, merge_request.target_project) + + allow(view).to receive(:can?).and_return(true) + allow(view).to receive(:url_for).and_return('#') + allow(view).to receive(:current_user).and_return(merge_request.author) + end + + context 'when there are pipelines for merge request but no pipeline for last commit' do + before do + assign(:pipelines, Ci::Pipeline.all) + assign(:pipeline, nil) + end + + it 'shows <> tab and hides <> tab' do + render + expect(rendered).to have_text('Pipelines 1') + expect(rendered).not_to have_text('Builds') + end + end +end -- cgit v1.2.1 From 9e6cdc64741583ed0db74485892c1970ff960eab Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Mon, 28 Nov 2016 13:00:42 +0100 Subject: Revert "Pass correct tag target to post-receive hook when creating tag via UI" This reverts commit ae51774bc45d2e15ccc61b01a30d1b588f179f85. --- app/models/repository.rb | 13 +++---------- spec/models/repository_spec.rb | 22 ---------------------- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index bf136ccdb6c..5e831f84879 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -196,18 +196,11 @@ class Repository options = { message: message, tagger: user_to_committer(user) } if message - rugged.tags.create(tag_name, target, options) - tag = find_tag(tag_name) - - GitHooksService.new.execute(user, path_to_repo, oldrev, tag.target, ref) do - # we already created a tag, because we need tag SHA to pass correct - # values to hooks + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do + rugged.tags.create(tag_name, target, options) end - tag - rescue GitHooksService::PreReceiveError - rugged.tags.delete(tag_name) - raise + find_tag(tag_name) end def rm_branch(user, branch_name) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 04afb8ebc98..214bf478d19 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1303,28 +1303,6 @@ describe Repository, models: true do repository.add_tag(user, '8.5', 'master', 'foo') end - it 'does not create a tag when a pre-hook fails' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) - - expect do - repository.add_tag(user, '8.5', 'master', 'foo') - end.to raise_error(GitHooksService::PreReceiveError) - - repository.expire_tags_cache - expect(repository.find_tag('8.5')).to be_nil - end - - it 'passes tag SHA to hooks' do - spy = GitHooksService.new - allow(GitHooksService).to receive(:new).and_return(spy) - allow(spy).to receive(:execute).and_call_original - - tag = repository.add_tag(user, '8.5', 'master', 'foo') - - expect(spy).to have_received(:execute). - with(anything, anything, anything, tag.target, anything) - end - it 'returns a Gitlab::Git::Tag object' do tag = repository.add_tag(user, '8.5', 'master', 'foo') -- cgit v1.2.1 From cf58271e11f6704523be5211ecfb2d02ae1091fe Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Mon, 28 Nov 2016 15:04:51 +0100 Subject: Pass tag SHA to post-receive hook when tag is created via UI We only know the tag SHA after we create the tag. This means that we pass a different value to the hooks that happen before creating the tag, and a different value to the hooks that happen after creating the tag. This is not an ideal situation, but it is a trade-off we decided to make. For discussion of the alternatives please refer to https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7700#note_18982873 "pre-receive" and "update" hooks always get the SHA of the commit that the tag points to. "post-receive" gets the tag SHA if it is an annotated tag or the commit SHA if it is an lightweight tag. Currently we always create annotated tags if UI is used. --- app/models/repository.rb | 5 +++-- app/services/git_hooks_service.rb | 6 +++-- ...-developer-access-can-no-longer-create-tags.yml | 4 ++++ spec/models/repository_spec.rb | 26 ++++++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml diff --git a/app/models/repository.rb b/app/models/repository.rb index 5e831f84879..e2e7d08abac 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -196,8 +196,9 @@ class Repository options = { message: message, tagger: user_to_committer(user) } if message - GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do - rugged.tags.create(tag_name, target, options) + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do |service| + raw_tag = rugged.tags.create(tag_name, target, options) + service.newrev = raw_tag.target_id end find_tag(tag_name) diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index 172bd85dade..6cd3908d43a 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -1,6 +1,8 @@ class GitHooksService PreReceiveError = Class.new(StandardError) + attr_accessor :oldrev, :newrev, :ref + def execute(user, repo_path, oldrev, newrev, ref) @repo_path = repo_path @user = Gitlab::GlId.gl_id(user) @@ -16,7 +18,7 @@ class GitHooksService end end - yield + yield self run_hook('post-receive') end @@ -25,6 +27,6 @@ class GitHooksService def run_hook(name) hook = Gitlab::Git::Hook.new(name, @repo_path) - hook.trigger(@user, @oldrev, @newrev, @ref) + hook.trigger(@user, oldrev, newrev, ref) end end diff --git a/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml b/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml new file mode 100644 index 00000000000..9254db40742 --- /dev/null +++ b/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml @@ -0,0 +1,4 @@ +--- +title: Pass tag SHA to post-receive hook when tag is created via UI +merge_request: 7700 +author: diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 214bf478d19..b797d19161d 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1308,6 +1308,32 @@ describe Repository, models: true do expect(tag).to be_a(Gitlab::Git::Tag) end + + it 'passes commit SHA to pre-receive and update hooks,\ + and tag SHA to post-receive hook' do + pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', repository.path_to_repo) + update_hook = Gitlab::Git::Hook.new('update', repository.path_to_repo) + post_receive_hook = Gitlab::Git::Hook.new('post-receive', repository.path_to_repo) + + allow(Gitlab::Git::Hook).to receive(:new). + and_return(pre_receive_hook, update_hook, post_receive_hook) + + allow(pre_receive_hook).to receive(:trigger).and_call_original + allow(update_hook).to receive(:trigger).and_call_original + allow(post_receive_hook).to receive(:trigger).and_call_original + + tag = repository.add_tag(user, '8.5', 'master', 'foo') + + commit_sha = repository.commit('master').id + tag_sha = tag.target + + expect(pre_receive_hook).to have_received(:trigger). + with(anything, anything, commit_sha, anything) + expect(update_hook).to have_received(:trigger). + with(anything, anything, commit_sha, anything) + expect(post_receive_hook).to have_received(:trigger). + with(anything, anything, tag_sha, anything) + end end context 'with an invalid target' do -- cgit v1.2.1 From 61abf53a622ad31f266f0eec1695fcb2e2bd27e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Mon, 28 Nov 2016 12:26:28 -0300 Subject: Update CHANGELOG.md for 8.14.1 [ci skip] --- CHANGELOG.md | 20 ++++++++++++++++++++ ...rces-in-administrator-settings-enable-disable.yml | 4 ---- ...ess-the-Orange-button-on-Merge-request-screen.yml | 4 ---- .../24739-collapsed-build-list-sorting.yml | 4 ---- ...9-last-deployment-call-on-nil-environment-fix.yml | 4 ---- ...n-projects-pipelinessettingscontroller-update.yml | 4 ---- .../24863-mrs-without-discussions-are-mergeable.yml | 4 ---- .../Last-minute-CI-Style-tweaks-for-8-14.yml | 4 ---- .../unreleased/disable-calendar-deselection.yml | 4 ---- .../fix-build-without-trace-exceptions.yml | 4 ---- .../unreleased/fix-cycle-analytics-plan-issue.yml | 4 ---- .../unreleased/fix_sidekiq_stats_in_admin_area.yml | 4 ---- changelogs/unreleased/issue-boards-dragging-fix.yml | 4 ---- changelogs/unreleased/zj-upgrade-grape.yml | 4 ---- 14 files changed, 20 insertions(+), 52 deletions(-) delete mode 100644 changelogs/unreleased/24161-non-intuitive-buttons-for-import-sources-in-administrator-settings-enable-disable.yml delete mode 100644 changelogs/unreleased/24266-Afraid-to-press-the-Orange-button-on-Merge-request-screen.yml delete mode 100644 changelogs/unreleased/24739-collapsed-build-list-sorting.yml delete mode 100644 changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml delete mode 100644 changelogs/unreleased/24804-wrong-render-index-should-be-render-show-in-projects-pipelinessettingscontroller-update.yml delete mode 100644 changelogs/unreleased/24863-mrs-without-discussions-are-mergeable.yml delete mode 100644 changelogs/unreleased/Last-minute-CI-Style-tweaks-for-8-14.yml delete mode 100644 changelogs/unreleased/disable-calendar-deselection.yml delete mode 100644 changelogs/unreleased/fix-build-without-trace-exceptions.yml delete mode 100644 changelogs/unreleased/fix-cycle-analytics-plan-issue.yml delete mode 100644 changelogs/unreleased/fix_sidekiq_stats_in_admin_area.yml delete mode 100644 changelogs/unreleased/issue-boards-dragging-fix.yml delete mode 100644 changelogs/unreleased/zj-upgrade-grape.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 549336e4dff..18b3755d365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.14.1 (2016-11-28) + +- Fix deselecting calendar days on contribution graph. !6453 (ClemMakesApps) +- Update grape entity to 0.6.0. !7491 +- If Build running change accept merge request when build succeeds button from orange to blue. !7577 +- Changed import sources buttons to checkboxes. !7598 (Luke "Jared" Bennett) +- Last minute CI Style tweaks for 8.14. !7643 +- Fix exceptions when loading build trace. !7658 +- Fix wrong template rendered when CI/CD settings aren't update successfully. !7665 +- fixes last_deployment call environment is nil. !7671 +- Sort builds by name within pipeline graph. !7681 +- Correctly determine mergeability of MR with no discussions. +- Sidekiq stats in the admin area will now show correctly on different platforms. (blackst0ne) +- Fixed issue boards dragging card removing random issues. +- Fix information disclosure in `Projects::BlobController#update`. +- Fix missing access checks on issue lookup using IssuableFinder. +- Replace issue access checks with use of IssuableFinder. +- Non members cannot create labels through the API. +- Fix cycle analytics plan stage when commits are missing. + ## 8.14.0 (2016-11-22) - Use separate email-token for incoming email and revert back the inactive feature. !5914 diff --git a/changelogs/unreleased/24161-non-intuitive-buttons-for-import-sources-in-administrator-settings-enable-disable.yml b/changelogs/unreleased/24161-non-intuitive-buttons-for-import-sources-in-administrator-settings-enable-disable.yml deleted file mode 100644 index 1404748e83e..00000000000 --- a/changelogs/unreleased/24161-non-intuitive-buttons-for-import-sources-in-administrator-settings-enable-disable.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Changed import sources buttons to checkboxes -merge_request: 7598 -author: Luke "Jared" Bennett diff --git a/changelogs/unreleased/24266-Afraid-to-press-the-Orange-button-on-Merge-request-screen.yml b/changelogs/unreleased/24266-Afraid-to-press-the-Orange-button-on-Merge-request-screen.yml deleted file mode 100644 index 28ca20c7dcc..00000000000 --- a/changelogs/unreleased/24266-Afraid-to-press-the-Orange-button-on-Merge-request-screen.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: If Build running change accept merge request when build succeeds button from orange to blue -merge_request: 7577 -author: diff --git a/changelogs/unreleased/24739-collapsed-build-list-sorting.yml b/changelogs/unreleased/24739-collapsed-build-list-sorting.yml deleted file mode 100644 index 036e606318f..00000000000 --- a/changelogs/unreleased/24739-collapsed-build-list-sorting.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Sort builds by name within pipeline graph -merge_request: 7681 -author: diff --git a/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml b/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml deleted file mode 100644 index 5e7580fb8f2..00000000000 --- a/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: fixes last_deployment call environment is nil -merge_request: 7671 -author: diff --git a/changelogs/unreleased/24804-wrong-render-index-should-be-render-show-in-projects-pipelinessettingscontroller-update.yml b/changelogs/unreleased/24804-wrong-render-index-should-be-render-show-in-projects-pipelinessettingscontroller-update.yml deleted file mode 100644 index 92dbbe3d164..00000000000 --- a/changelogs/unreleased/24804-wrong-render-index-should-be-render-show-in-projects-pipelinessettingscontroller-update.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix wrong template rendered when CI/CD settings aren't update successfully -merge_request: 7665 -author: diff --git a/changelogs/unreleased/24863-mrs-without-discussions-are-mergeable.yml b/changelogs/unreleased/24863-mrs-without-discussions-are-mergeable.yml deleted file mode 100644 index 9bdb9411135..00000000000 --- a/changelogs/unreleased/24863-mrs-without-discussions-are-mergeable.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Correctly determine mergeability of MR with no discussions -merge_request: -author: diff --git a/changelogs/unreleased/Last-minute-CI-Style-tweaks-for-8-14.yml b/changelogs/unreleased/Last-minute-CI-Style-tweaks-for-8-14.yml deleted file mode 100644 index 7d49c639a43..00000000000 --- a/changelogs/unreleased/Last-minute-CI-Style-tweaks-for-8-14.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Last minute CI Style tweaks for 8.14 -merge_request: 7643 -author: diff --git a/changelogs/unreleased/disable-calendar-deselection.yml b/changelogs/unreleased/disable-calendar-deselection.yml deleted file mode 100644 index 060797bba34..00000000000 --- a/changelogs/unreleased/disable-calendar-deselection.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix deselecting calendar days on contribution graph -merge_request: 6453 -author: ClemMakesApps diff --git a/changelogs/unreleased/fix-build-without-trace-exceptions.yml b/changelogs/unreleased/fix-build-without-trace-exceptions.yml deleted file mode 100644 index 3b95e96e212..00000000000 --- a/changelogs/unreleased/fix-build-without-trace-exceptions.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix exceptions when loading build trace -merge_request: 7658 -author: diff --git a/changelogs/unreleased/fix-cycle-analytics-plan-issue.yml b/changelogs/unreleased/fix-cycle-analytics-plan-issue.yml deleted file mode 100644 index 6ed16c6d722..00000000000 --- a/changelogs/unreleased/fix-cycle-analytics-plan-issue.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix cycle analytics plan stage when commits are missing -merge_request: -author: diff --git a/changelogs/unreleased/fix_sidekiq_stats_in_admin_area.yml b/changelogs/unreleased/fix_sidekiq_stats_in_admin_area.yml deleted file mode 100644 index 4f007be8624..00000000000 --- a/changelogs/unreleased/fix_sidekiq_stats_in_admin_area.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Sidekiq stats in the admin area will now show correctly on different platforms -merge_request: -author: blackst0ne diff --git a/changelogs/unreleased/issue-boards-dragging-fix.yml b/changelogs/unreleased/issue-boards-dragging-fix.yml deleted file mode 100644 index 565e09b930b..00000000000 --- a/changelogs/unreleased/issue-boards-dragging-fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed issue boards dragging card removing random issues -merge_request: -author: diff --git a/changelogs/unreleased/zj-upgrade-grape.yml b/changelogs/unreleased/zj-upgrade-grape.yml deleted file mode 100644 index 1df42d98733..00000000000 --- a/changelogs/unreleased/zj-upgrade-grape.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update grape entity to 0.6.0 -merge_request: 7491 -author: -- cgit v1.2.1 From d5b662c2a7512f6c419cb22faab1e08009b7c050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 28 Nov 2016 16:32:01 +0100 Subject: Update CHANGELOG.md for 8.13.7 [ci skip] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b3755d365..12a3e63ed2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -222,6 +222,15 @@ entry. - Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix 404 when visit /projects page +## 8.13.7 (2016-11-28) + +- fixes 500 error on project show when user is not logged in and project is still empty. !7376 +- Update grape entity to 0.6.0. !7491 +- Fix information disclosure in `Projects::BlobController#update`. +- Fix missing access checks on issue lookup using IssuableFinder. +- Replace issue access checks with use of IssuableFinder. +- Non members cannot create labels through the API. + ## 8.13.6 (2016-11-17) - Omniauth auto link LDAP user falls back to find by DN when user cannot be found by UID. !7002 -- cgit v1.2.1 From c33b489853eb025c4d2d9c4a79630109ddf55e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 28 Nov 2016 16:45:08 +0100 Subject: Add `null: true` to timestamps in migrations that does not define it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is to ensure that migrations will still be consitent when we will upgrade to Rails 5 which default to `null: false` for timestamps columns. Fixes #23666. Signed-off-by: Rémy Coutable --- db/migrate/20130319214458_create_forked_project_links.rb | 2 +- db/migrate/20130506090604_create_deploy_keys_projects.rb | 2 +- db/migrate/20130617095603_create_users_groups.rb | 2 +- db/migrate/20130711063759_create_project_group_links.rb | 2 +- db/migrate/20131112114325_create_broadcast_messages.rb | 2 +- db/migrate/20140122112253_create_merge_request_diffs.rb | 2 +- db/migrate/20140209025651_create_emails.rb | 2 +- db/migrate/20140625115202_create_users_star_projects.rb | 2 +- db/migrate/20140729134820_create_labels.rb | 2 +- db/migrate/20140729140420_create_label_links.rb | 2 +- db/migrate/20140914113604_add_members_table.rb | 2 +- db/migrate/20140914173417_remove_old_member_tables.rb | 4 ++-- db/migrate/20141118150935_add_audit_event.rb | 2 +- db/migrate/20141216155758_create_doorkeeper_tables.rb | 2 +- db/migrate/20150108073740_create_application_settings.rb | 2 +- db/migrate/20150313012111_create_subscriptions_table.rb | 2 +- db/migrate/20150806104937_create_abuse_reports.rb | 2 +- db/migrate/20151103134857_create_lfs_objects.rb | 2 +- db/migrate/20151103134958_create_lfs_objects_projects.rb | 2 +- db/migrate/20151105094515_create_releases.rb | 2 +- db/migrate/20160212123307_create_tasks.rb | 2 +- db/migrate/20160416180807_add_award_emoji.rb | 2 +- db/migrate/20160831214002_create_project_features.rb | 2 +- 23 files changed, 24 insertions(+), 24 deletions(-) diff --git a/db/migrate/20130319214458_create_forked_project_links.rb b/db/migrate/20130319214458_create_forked_project_links.rb index 66eb11a4b2b..41b0b700a6f 100644 --- a/db/migrate/20130319214458_create_forked_project_links.rb +++ b/db/migrate/20130319214458_create_forked_project_links.rb @@ -5,7 +5,7 @@ class CreateForkedProjectLinks < ActiveRecord::Migration t.integer :forked_to_project_id, null: false t.integer :forked_from_project_id, null: false - t.timestamps + t.timestamps null: true end add_index :forked_project_links, :forked_to_project_id, unique: true end diff --git a/db/migrate/20130506090604_create_deploy_keys_projects.rb b/db/migrate/20130506090604_create_deploy_keys_projects.rb index 7d6662d358a..f2e416d3b6f 100644 --- a/db/migrate/20130506090604_create_deploy_keys_projects.rb +++ b/db/migrate/20130506090604_create_deploy_keys_projects.rb @@ -5,7 +5,7 @@ class CreateDeployKeysProjects < ActiveRecord::Migration t.integer :deploy_key_id, null: false t.integer :project_id, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb index 45cff93fe4a..cb098aa9bf9 100644 --- a/db/migrate/20130617095603_create_users_groups.rb +++ b/db/migrate/20130617095603_create_users_groups.rb @@ -6,7 +6,7 @@ class CreateUsersGroups < ActiveRecord::Migration t.integer :group_id, null: false t.integer :user_id, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb index bd9d40a50db..8da7ff6f4cd 100644 --- a/db/migrate/20130711063759_create_project_group_links.rb +++ b/db/migrate/20130711063759_create_project_group_links.rb @@ -5,7 +5,7 @@ class CreateProjectGroupLinks < ActiveRecord::Migration t.integer :project_id, null: false t.integer :group_id, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb index ce37a8e2708..4ada40f1b66 100644 --- a/db/migrate/20131112114325_create_broadcast_messages.rb +++ b/db/migrate/20131112114325_create_broadcast_messages.rb @@ -7,7 +7,7 @@ class CreateBroadcastMessages < ActiveRecord::Migration t.datetime :ends_at t.integer :alert_type - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb index 395c3edfc79..68448c91529 100644 --- a/db/migrate/20140122112253_create_merge_request_diffs.rb +++ b/db/migrate/20140122112253_create_merge_request_diffs.rb @@ -7,7 +7,7 @@ class CreateMergeRequestDiffs < ActiveRecord::Migration t.text :st_diffs, null: true t.integer :merge_request_id, null: false - t.timestamps + t.timestamps null: true end if ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/ diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb index 571beb19cdd..48d14682628 100644 --- a/db/migrate/20140209025651_create_emails.rb +++ b/db/migrate/20140209025651_create_emails.rb @@ -5,7 +5,7 @@ class CreateEmails < ActiveRecord::Migration t.integer :user_id, null: false t.string :email, null: false - t.timestamps + t.timestamps null: true end add_index :emails, :user_id diff --git a/db/migrate/20140625115202_create_users_star_projects.rb b/db/migrate/20140625115202_create_users_star_projects.rb index 32dd99e83be..c50bc4bd614 100644 --- a/db/migrate/20140625115202_create_users_star_projects.rb +++ b/db/migrate/20140625115202_create_users_star_projects.rb @@ -4,7 +4,7 @@ class CreateUsersStarProjects < ActiveRecord::Migration create_table :users_star_projects do |t| t.integer :project_id, null: false t.integer :user_id, null: false - t.timestamps + t.timestamps null: true end add_index :users_star_projects, :user_id add_index :users_star_projects, :project_id diff --git a/db/migrate/20140729134820_create_labels.rb b/db/migrate/20140729134820_create_labels.rb index df0f8cb9f03..589aced0d76 100644 --- a/db/migrate/20140729134820_create_labels.rb +++ b/db/migrate/20140729134820_create_labels.rb @@ -6,7 +6,7 @@ class CreateLabels < ActiveRecord::Migration t.string :color t.integer :project_id - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20140729140420_create_label_links.rb b/db/migrate/20140729140420_create_label_links.rb index fa5992605f8..abdbaa1bd1a 100644 --- a/db/migrate/20140729140420_create_label_links.rb +++ b/db/migrate/20140729140420_create_label_links.rb @@ -6,7 +6,7 @@ class CreateLabelLinks < ActiveRecord::Migration t.integer :target_id t.string :target_type - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb index bc3c1bb61e4..fb0876dd520 100644 --- a/db/migrate/20140914113604_add_members_table.rb +++ b/db/migrate/20140914113604_add_members_table.rb @@ -9,7 +9,7 @@ class AddMembersTable < ActiveRecord::Migration t.integer :notification_level, null: false t.string :type - t.timestamps + t.timestamps null: true end add_index :members, :type diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb index aff8e94e5be..067dc21ccf1 100644 --- a/db/migrate/20140914173417_remove_old_member_tables.rb +++ b/db/migrate/20140914173417_remove_old_member_tables.rb @@ -12,7 +12,7 @@ class RemoveOldMemberTables < ActiveRecord::Migration t.integer :user_id, null: false t.integer :notification_level, null: false, default: 3 - t.timestamps + t.timestamps null: true end create_table :users_projects do |t| @@ -21,7 +21,7 @@ class RemoveOldMemberTables < ActiveRecord::Migration t.integer :user_id, null: false t.integer :notification_level, null: false, default: 3 - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb index 3884228456f..f0452f46459 100644 --- a/db/migrate/20141118150935_add_audit_event.rb +++ b/db/migrate/20141118150935_add_audit_event.rb @@ -13,7 +13,7 @@ class AddAuditEvent < ActiveRecord::Migration # Details for the event t.text :details - t.timestamps + t.timestamps null: true end add_index :audit_events, :author_id diff --git a/db/migrate/20141216155758_create_doorkeeper_tables.rb b/db/migrate/20141216155758_create_doorkeeper_tables.rb index b323ffe96f5..1c4d32e133c 100644 --- a/db/migrate/20141216155758_create_doorkeeper_tables.rb +++ b/db/migrate/20141216155758_create_doorkeeper_tables.rb @@ -7,7 +7,7 @@ class CreateDoorkeeperTables < ActiveRecord::Migration t.string :secret, null: false t.text :redirect_uri, null: false t.string :scopes, null: false, default: '' - t.timestamps + t.timestamps null: true end add_index :oauth_applications, :uid, unique: true diff --git a/db/migrate/20150108073740_create_application_settings.rb b/db/migrate/20150108073740_create_application_settings.rb index dfa2f765357..c26a7c39574 100644 --- a/db/migrate/20150108073740_create_application_settings.rb +++ b/db/migrate/20150108073740_create_application_settings.rb @@ -8,7 +8,7 @@ class CreateApplicationSettings < ActiveRecord::Migration t.boolean :gravatar_enabled t.text :sign_in_text - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb index 8adb193b27f..0977c9adfec 100644 --- a/db/migrate/20150313012111_create_subscriptions_table.rb +++ b/db/migrate/20150313012111_create_subscriptions_table.rb @@ -6,7 +6,7 @@ class CreateSubscriptionsTable < ActiveRecord::Migration t.references :subscribable, polymorphic: true t.boolean :subscribed - t.timestamps + t.timestamps null: true end add_index :subscriptions, diff --git a/db/migrate/20150806104937_create_abuse_reports.rb b/db/migrate/20150806104937_create_abuse_reports.rb index 3c749b5d9a9..9f1512db862 100644 --- a/db/migrate/20150806104937_create_abuse_reports.rb +++ b/db/migrate/20150806104937_create_abuse_reports.rb @@ -6,7 +6,7 @@ class CreateAbuseReports < ActiveRecord::Migration t.integer :user_id t.text :message - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb index 745b52e2b24..fadaf637cec 100644 --- a/db/migrate/20151103134857_create_lfs_objects.rb +++ b/db/migrate/20151103134857_create_lfs_objects.rb @@ -5,7 +5,7 @@ class CreateLfsObjects < ActiveRecord::Migration t.string :oid, null: false, unique: true t.integer :size, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb index 3178e85b899..e830cbe4656 100644 --- a/db/migrate/20151103134958_create_lfs_objects_projects.rb +++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb @@ -5,7 +5,7 @@ class CreateLfsObjectsProjects < ActiveRecord::Migration t.integer :lfs_object_id, null: false t.integer :project_id, null: false - t.timestamps + t.timestamps null: true end add_index :lfs_objects_projects, :project_id diff --git a/db/migrate/20151105094515_create_releases.rb b/db/migrate/20151105094515_create_releases.rb index 145b8db1486..87f692c64d0 100644 --- a/db/migrate/20151105094515_create_releases.rb +++ b/db/migrate/20151105094515_create_releases.rb @@ -6,7 +6,7 @@ class CreateReleases < ActiveRecord::Migration t.text :description t.integer :project_id - t.timestamps + t.timestamps null: true end add_index :releases, :project_id diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb index 20573b01351..8b5b1dd694d 100644 --- a/db/migrate/20160212123307_create_tasks.rb +++ b/db/migrate/20160212123307_create_tasks.rb @@ -9,7 +9,7 @@ class CreateTasks < ActiveRecord::Migration t.integer :action, null: false t.string :state, null: false, index: true - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20160416180807_add_award_emoji.rb b/db/migrate/20160416180807_add_award_emoji.rb index a3bee9b1bc6..c0957f028a8 100644 --- a/db/migrate/20160416180807_add_award_emoji.rb +++ b/db/migrate/20160416180807_add_award_emoji.rb @@ -6,7 +6,7 @@ class AddAwardEmoji < ActiveRecord::Migration t.references :user t.references :awardable, polymorphic: true - t.timestamps + t.timestamps null: true end add_index :award_emoji, :user_id diff --git a/db/migrate/20160831214002_create_project_features.rb b/db/migrate/20160831214002_create_project_features.rb index 2d76a015a08..343953826f0 100644 --- a/db/migrate/20160831214002_create_project_features.rb +++ b/db/migrate/20160831214002_create_project_features.rb @@ -10,7 +10,7 @@ class CreateProjectFeatures < ActiveRecord::Migration t.integer :snippets_access_level t.integer :builds_access_level - t.timestamps + t.timestamps null: true end end end -- cgit v1.2.1 From beedd40ef7744151d87f4d3ba0b47b2878a83195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 28 Nov 2016 13:03:31 +0100 Subject: Ensure user is authenticated to create a new snippet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/concerns/toggle_award_emoji.rb | 5 +---- .../25026-authenticate-user-for-new-snippet.yml | 4 ++++ spec/controllers/snippets_controller_spec.rb | 22 ++++++++++++++++++++++ spec/features/snippets/create_snippet_spec.rb | 20 ++++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/25026-authenticate-user-for-new-snippet.yml create mode 100644 spec/features/snippets/create_snippet_spec.rb diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index 3717c49f272..fbf9a026b10 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -1,11 +1,8 @@ module ToggleAwardEmoji extend ActiveSupport::Concern - included do - before_action :authenticate_user!, only: [:toggle_award_emoji] - end - def toggle_award_emoji + authenticate_user! name = params.require(:name) if awardable.user_can_award?(current_user, name) diff --git a/changelogs/unreleased/25026-authenticate-user-for-new-snippet.yml b/changelogs/unreleased/25026-authenticate-user-for-new-snippet.yml new file mode 100644 index 00000000000..a7b5810f1bf --- /dev/null +++ b/changelogs/unreleased/25026-authenticate-user-for-new-snippet.yml @@ -0,0 +1,4 @@ +--- +title: Redirect to sign-in page when unauthenticated user tries to create a snippet +merge_request: 7786 +author: diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 2d762fdaa04..d76fe9f580f 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -3,6 +3,28 @@ require 'spec_helper' describe SnippetsController do let(:user) { create(:user) } + describe 'GET #new' do + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with status 200' do + get :new + + expect(response).to have_http_status(200) + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :new + + expect(response).to redirect_to(new_user_session_path) + end + end + end + describe 'GET #show' do context 'when the personal snippet is private' do let(:personal_snippet) { create(:personal_snippet, :private, author: user) } diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb new file mode 100644 index 00000000000..cb95e7828db --- /dev/null +++ b/spec/features/snippets/create_snippet_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +feature 'Create Snippet', feature: true do + before do + login_as :user + visit new_snippet_path + end + + scenario 'Authenticated user creates a snippet' do + fill_in 'personal_snippet_title', with: 'My Snippet Title' + page.within('.file-editor') do + find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!' + end + + click_button 'Create snippet' + + expect(page).to have_content('My Snippet Title') + expect(page).to have_content('Hello World!') + end +end -- cgit v1.2.1 From 8b265aa3cadf9a9c4727495ff4fa9f2aa3a39375 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 28 Nov 2016 11:13:17 -0500 Subject: Show dashes when date is empty --- .../cycle_analytics/components/total_time_component.js.es6 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 index b9675f50e31..0d85e1a4678 100644 --- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 @@ -10,10 +10,15 @@ }, template: ` - - - - + + `, }); -- cgit v1.2.1 From 6743d5aad0b6fc410919170c393780ca17d2437b Mon Sep 17 00:00:00 2001 From: Chris Peressini Date: Mon, 28 Nov 2016 16:34:47 +0000 Subject: Create secondary colors with darken function --- app/assets/stylesheets/framework/variables.scss | 72 +++++++++++++------------ 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 750d99ebabe..2539c841111 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,67 +12,71 @@ $sidebar-breakpoint: 1024px; /* * Color schema */ + $darken-normal-factor: 7%; +$darken-dark-factor: 10%; +$darken-border-factor: 5%; + $white-light: #fff; -$white-normal: #ededed; -$white-dark: #ececec; +$white-normal: darken($white-light, $darken-normal-factor); +$white-dark: darken($white-light, $darken-dark-factor); $gray-lightest: #fdfdfd; $gray-light: #fafafa; $gray-lighter: #f9f9f9; -$gray-normal: #f5f5f5; -$gray-dark: #ededed; +$gray-normal: darken($gray-light, $darken-normal-factor); +$gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c9c9c9; -$green-light: #38ae67; -$green-normal: #2faa60; -$green-dark: #2ca05b; +$green-light: #3cbd70; +$green-normal: darken($green-light, $darken-normal-factor); +$green-dark: darken($green-light, $darken-dark-factor); $blue-light: #2ea8e5; -$blue-normal: #2d9fd8; -$blue-dark: #2897ce; +$blue-normal: darken($blue-light, $darken-normal-factor); +$blue-dark: darken($blue-light, $darken-dark-factor); $blue-medium-light: #3498cb; -$blue-medium: #2f8ebf; -$blue-medium-dark: #2d86b4; +$blue-medium: darken($blue-medium-light, $darken-normal-factor); +$blue-medium-dark: darken($blue-medium-light, $darken-dark-factor); $blue-light-transparent: rgba(44, 159, 216, 0.05); $orange-light: #fc8a51; -$orange-normal: #e75e40; -$orange-dark: #ce5237; +$orange-normal: darken($orange-light, $darken-normal-factor); +$orange-dark: darken($orange-light, $darken-dark-factor); $red-light: #e52c5a; -$red-normal: #d22852; -$red-dark: darken($red-normal, 5%); +$red-normal: darken($red-light, $darken-normal-factor); +$red-dark: darken($red-light, $darken-dark-factor); $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); -$border-white-light: #f1f2f4; -$border-white-normal: #d6dae2; -$border-white-dark: #c6cacf; +$border-white-light: darken($white-light, $darken-border-factor); +$border-white-normal: darken($white-normal, $darken-border-factor); +$border-white-dark: darken($white-dark, $darken-border-factor); -$border-gray-light: #dcdcdc; -$border-gray-normal: #d7d7d7; -$border-gray-dark: #c6cacf; +$border-gray-light: darken($gray-light, $darken-border-factor); +$border-gray-normal: darken($gray-normal, $darken-border-factor); +$border-gray-dark: darken($gray-dark, $darken-border-factor); $border-green-extra-light: #9adb84; -$border-green-light: #2faa60; -$border-green-normal: #2ca05b; -$border-green-dark: #279654; +$border-green-light: darken($green-light, $darken-border-factor); +$border-green-normal: darken($green-normal, $darken-border-factor); +$border-green-dark: darken($green-dark, $darken-border-factor); -$border-blue-light: #2d9fd8; -$border-blue-normal: #2897ce; -$border-blue-dark: #258dc1; +$border-blue-light: darken($blue-light, $darken-border-factor); +$border-blue-normal: darken($blue-normal, $darken-border-factor); +$border-blue-dark: darken($blue-dark, $darken-border-factor); -$border-orange-light: #fc6d26; -$border-orange-normal: #ce5237; -$border-orange-dark: #c14e35; +$border-orange-light: darken($orange-light, $darken-border-factor); +$border-orange-normal: darken($orange-normal, $darken-border-factor); +$border-orange-dark: darken($orange-dark, $darken-border-factor); -$border-red-light: #d22852; -$border-red-normal: #ca264f; -$border-red-dark: darken($border-red-normal, 5%); +$border-red-light: darken($red-light, $darken-border-factor); +$border-red-normal: darken($red-normal, $darken-border-factor); +$border-red-dark: darken($red-dark, $darken-border-factor); $help-well-bg: $gray-light; $help-well-border: #e5e5e5; @@ -255,7 +259,7 @@ $search-input-border-color: rgba(#4688f1, .8); $search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-width: 220px; $location-badge-color: #aaa; -$location-badge-bg: $gray-normal; +$location-badge-bg: $dark-background-color; $location-badge-active-bg: #4f91f8; $location-icon-color: #e7e9ed; $location-icon-active-color: #807e7e; -- cgit v1.2.1 From 297c8683982c4ee83fc6b866f121b6aa18f5488a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 28 Nov 2016 18:27:29 +0100 Subject: Add guidelines in doc linking with HAML [ci skip] --- doc/development/doc_styleguide.md | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index b137e6ae82e..fc948a7a116 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -113,6 +113,77 @@ merge request. add an alternative text: `[identifier]: https://example.com "Alternative text"` that appears when hovering your mouse on a link +### Linking to inline docs + +Sometimes it's needed to link to the built-in documentation that GitLab provides +under `/help`. This is normally done in files inside the `app/views/` directory +with the help of the `help_page_path` helper method. + +In its simplest form, the HAML code to generate a link to the `/help` page is: + +```haml += link_to 'Help page', help_page_path('user/permissions') +``` + +The `help_page_path` contains the path to the document you want to link to with +the following conventions: + +- it is relative to the `doc/` directory in the GitLab repository +- the `.md` extension must be omitted +- it must not end with a slash (`/`) + +Below are some special cases where should be used depending on the context. +You can combine one or more of the following: + +1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path` + method: + + ```haml + = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link') + ``` + +1. **Opening links in a new tab.** This should be the default behavior: + + ```haml + = link_to 'Help page', help_page_path('user/permissions'), target: '_blank' + ``` + +1. **Linking to a circle icon.** Usually used in settings where a long + description cannot be used, like near checkboxes. You can basically use + any font awesome icon, but prefer the `question-circle`: + + ```haml + = link_to icon('question-circle'), help_page_path('user/permissions') + ``` + +1. **Using a button link.** Useful in places where text would be out of context + with the rest of the page layout: + + ```haml + = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info' + ``` + +1. **Underlining a link.** + + ```haml + = link_to 'Help page', help_page_path('user/permissions'), class: 'underlined-link' + ``` + +1. **Using links inline of some text.** + + ```haml + Description to #{link_to 'Help page', help_page_path('user/permissions')}. + ``` + +1. **Adding a period at the end of the sentence.** Useful when you don't want + the period to be part of the link: + + ```haml + = succeed '.' do + Learn more in the + = link_to 'Help page', help_page_path('user/permissions') + ``` + ## Images - Place images in a separate directory named `img/` in the same directory where -- cgit v1.2.1 From eb4f15571d02634920b975e7ce2325572d88356e Mon Sep 17 00:00:00 2001 From: Livier Date: Wed, 23 Nov 2016 13:14:08 -0700 Subject: Changed API spec files to describe the correct class Restore changes for api spec files Fix error in rspec Users Delete extra space Repositories-spec --- changelogs/unreleased/update-api-spec-files.yml | 4 ++++ spec/requests/api/award_emoji_spec.rb | 2 +- spec/requests/api/boards_spec.rb | 2 +- spec/requests/api/branches_spec.rb | 2 +- spec/requests/api/builds_spec.rb | 2 +- spec/requests/api/commits_spec.rb | 2 +- spec/requests/api/deploy_keys_spec.rb | 2 +- spec/requests/api/deployments_spec.rb | 2 +- spec/requests/api/environments_spec.rb | 2 +- spec/requests/api/files_spec.rb | 2 +- spec/requests/api/groups_spec.rb | 2 +- spec/requests/api/internal_spec.rb | 2 +- spec/requests/api/issues_spec.rb | 2 +- spec/requests/api/keys_spec.rb | 2 +- spec/requests/api/labels_spec.rb | 2 +- spec/requests/api/merge_request_diffs_spec.rb | 2 +- spec/requests/api/merge_requests_spec.rb | 2 +- spec/requests/api/milestones_spec.rb | 2 +- spec/requests/api/namespaces_spec.rb | 2 +- spec/requests/api/notes_spec.rb | 2 +- spec/requests/api/notification_settings_spec.rb | 2 +- spec/requests/api/pipelines_spec.rb | 2 +- spec/requests/api/project_hooks_spec.rb | 2 +- spec/requests/api/project_snippets_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 2 +- spec/requests/api/repositories_spec.rb | 5 ++--- spec/requests/api/services_spec.rb | 2 +- spec/requests/api/session_spec.rb | 2 +- spec/requests/api/settings_spec.rb | 2 +- spec/requests/api/system_hooks_spec.rb | 2 +- spec/requests/api/tags_spec.rb | 2 +- spec/requests/api/triggers_spec.rb | 2 +- spec/requests/api/users_spec.rb | 2 +- spec/requests/api/variables_spec.rb | 2 +- spec/requests/api/version_spec.rb | 2 +- spec/requests/ci/api/builds_spec.rb | 2 +- spec/requests/ci/api/runners_spec.rb | 2 +- spec/requests/ci/api/triggers_spec.rb | 2 +- 38 files changed, 42 insertions(+), 39 deletions(-) create mode 100644 changelogs/unreleased/update-api-spec-files.yml diff --git a/changelogs/unreleased/update-api-spec-files.yml b/changelogs/unreleased/update-api-spec-files.yml new file mode 100644 index 00000000000..349d866cf22 --- /dev/null +++ b/changelogs/unreleased/update-api-spec-files.yml @@ -0,0 +1,4 @@ +--- +title: Update API spec files to describe the correct class +merge_request: +author: Livier diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 5ad4fc4865a..c8e8f31cc1f 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::AwardEmoji, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:empty_project) } diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 4f5c09a3029..3019724f52e 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Boards, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index fe6b875b997..ce1f2c60537 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Branches, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index fc72a44d663..0ea991b18b8 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Builds, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index a6e8550fac3..52491e1b9ed 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Commits, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 65897edba7f..5fa7299044e 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::DeployKeys, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 8fa8c66db6c..31e3cfa1b2f 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Deployments, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 1898b07835d..126496c43a5 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Environments, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 050d0dd082d..2081f80ccc1 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Files, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index d9fdafde05e..548ed8e1892 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Groups, api: true do include ApiHelpers let(:user1) { create(:user, can_create_group: false) } diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index e88a7e27d45..35644bd8cc9 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Internal, api: true do include ApiHelpers let(:user) { create(:user) } let(:key) { create(:key, user: user) } diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 7bae055b241..e385456dfbe 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Issues, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 893ed5c2b10..4c80987d680 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Keys, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index aaf41639277..b29ce1ea25e 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Labels, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index 131c2d406ea..e1887138aab 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, 'MergeRequestDiffs', api: true do +describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do include ApiHelpers let!(:user) { create(:user) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 37fcb2bc3a9..40534671522 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, api: true do +describe API::MergeRequests, api: true do include ApiHelpers let(:base_time) { Time.now } let(:user) { create(:user) } diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index b0946a838a1..8beef821d6c 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Milestones, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:empty_project, namespace: user.namespace ) } diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 5347cf4f7bc..c1edf384d5c 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Namespaces, api: true do include ApiHelpers let(:admin) { create(:admin) } let(:user) { create(:user) } diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 0124b7271b3..e44ef8e765d 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Notes, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, :public, namespace: user.namespace) } diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index e6d8a5ee954..8691a81420f 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::NotificationSettings, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index d83f7883c78..511007486f3 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Pipelines, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 5f39329a1b8..a42cedae614 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, 'ProjectHooks', api: true do +describe API::ProjectHooks, 'ProjectHooks', api: true do include ApiHelpers let(:user) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 1c25fd04339..01032c0929b 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe API::API, api: true do +describe API::ProjectSnippets, api: true do include ApiHelpers let(:project) { create(:empty_project, :public) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e53ee2a4e76..cde774d9cfa 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- require 'spec_helper' -describe API::API, api: true do +describe API::Projects, api: true do include ApiHelpers include Gitlab::CurrentSettings let(:user) { create(:user) } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 38c8ad34f9d..c90b69e8ebb 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Repositories, api: true do include ApiHelpers include RepoHelpers include WorkhorseHelpers @@ -44,7 +44,6 @@ describe API::API, api: true do end end - describe 'GET /projects/:id/repository/tree?recursive=1' do context 'authorized user' do before { project.team << [user2, :reporter] } @@ -67,7 +66,7 @@ describe API::API, api: true do expect(json_response).to be_an Object json_response['message'] == '404 Tree Not Found' - end + end end context "unauthorized user" do diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index ce9c96ace21..bf95eaff96a 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, api: true do +describe API::Services, api: true do include ApiHelpers let(:user) { create(:user) } let(:admin) { create(:admin) } diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index e3f22b4c578..794e2b5c04d 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Session, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 096a8ebab70..9a8d633d657 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, 'Settings', api: true do +describe API::Settings, 'Settings', api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 6c9df21f598..b3e5afdadb1 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::SystemHooks, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index d563883cd47..06fa94fae87 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Tags, api: true do include ApiHelpers include RepoHelpers diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index c890a51ae42..67ec3168679 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API do +describe API::Triggers do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 1a6e7716b2f..f82f52e7399 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Users, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 05fbdb909dc..7435f320607 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Variables, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 54b69a0cae7..da1b2fda70e 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Version, api: true do include ApiHelpers describe 'GET /version' do diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index a09d8689ff2..80652129928 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::API::API do +describe Ci::API::Builds do include ApiHelpers let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index d6c26fd8a94..bd55934d0c8 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::API::API do +describe Ci::API::Runners do include ApiHelpers include StubGitlabCalls diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 0a0f979f57d..2d434ab5dd8 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::API::API do +describe Ci::API::Triggers do include ApiHelpers describe 'POST /projects/:project_id/refs/:ref/trigger' do -- cgit v1.2.1 From ebfaaffef1e750b214979ec94d0b98b5d4beb200 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Fri, 18 Nov 2016 11:24:08 -0800 Subject: Add hover state to navigation rows --- app/assets/stylesheets/framework/nav.scss | 51 +++++++++++-------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ce864c2de5e..a2787ede53c 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -51,19 +51,26 @@ margin-bottom: -1px; font-size: 15px; line-height: 28px; - color: #959494; + color: $note-toolbar-color; border-bottom: 2px solid transparent; &:hover, &:active, &:focus { text-decoration: none; + border-bottom: 2px solid $gray-darkest; + color: $black; + + .badge { + color: $black; + } } } &.active a { border-bottom: 2px solid $link-underline-blue; color: $black; + font-weight: 600; } .badge { @@ -85,14 +92,20 @@ li { + &.active a { + border-bottom: none; + } + a { margin: 0; padding: 11px 10px 9px; - } - &.active a { - border-bottom: none; - color: $link-underline-blue; + &:hover, + &:active, + &:focus { + color: $black; + border-bottom: none; + } } } } @@ -310,37 +323,9 @@ height: 51px; li { - a { padding-top: 10px; } - - a, - i { - color: $layout-link-gray; - } - - &.active { - - a, - i { - color: $black; - } - - svg { - path, - polygon { - fill: $black; - } - } - } - - &:hover { - a, - i { - color: $black; - } - } } } } -- cgit v1.2.1 From bc1b305dcfff3c4818e6e3d1315234db9a555d61 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 23 Nov 2016 01:48:14 +0100 Subject: Replace static fixture for right_sidebar_spec (!7687) --- changelogs/unreleased/right-sidebar-fixture.yml | 4 ++++ spec/javascripts/fixtures/right_sidebar.html.haml | 17 ----------------- spec/javascripts/right_sidebar_spec.js | 14 ++++++-------- 3 files changed, 10 insertions(+), 25 deletions(-) create mode 100644 changelogs/unreleased/right-sidebar-fixture.yml delete mode 100644 spec/javascripts/fixtures/right_sidebar.html.haml diff --git a/changelogs/unreleased/right-sidebar-fixture.yml b/changelogs/unreleased/right-sidebar-fixture.yml new file mode 100644 index 00000000000..46a3e459fef --- /dev/null +++ b/changelogs/unreleased/right-sidebar-fixture.yml @@ -0,0 +1,4 @@ +--- +title: Replace static fixture for right_sidebar_spec +merge_request: 7687 +author: winniehell diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml deleted file mode 100644 index d259b58f235..00000000000 --- a/spec/javascripts/fixtures/right_sidebar.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -%div - %div.page-gutter.page-with-sidebar - - %aside.right-sidebar - %div.block.issuable-sidebar-header - %a.gutter-toggle.pull-right.js-sidebar-toggle - %i.fa.fa-angle-double-left - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} - %span.js-issuable-todo-text - Add todo - %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden - - %form.issuable-context-form - %div.block.labels - %div.sidebar-collapsed-icon - %i.fa.fa-tags - %span 1 diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 83ebbd63f3a..0a9bc546144 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -34,9 +34,10 @@ }; describe('RightSidebar', function() { - fixture.preload('right_sidebar.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('right_sidebar.html'); + fixture.load(fixtureName); this.sidebar = new Sidebar; $aside = $('.right-sidebar'); $page = $('.page-with-sidebar'); @@ -44,15 +45,12 @@ $toggle = $aside.find('.js-sidebar-toggle'); return $labelsIcon = $aside.find('.sidebar-collapsed-icon'); }); - it('should expand the sidebar when arrow is clicked', function() { + it('should expand/collapse the sidebar when arrow is clicked', function() { + assertSidebarState('expanded'); $toggle.click(); - return assertSidebarState('expanded'); - }); - it('should collapse the sidebar when arrow is clicked', function() { + assertSidebarState('collapsed'); $toggle.click(); assertSidebarState('expanded'); - $toggle.click(); - return assertSidebarState('collapsed'); }); it('should float over the page and when sidebar icons clicked', function() { $labelsIcon.click(); -- cgit v1.2.1 From 854fbbfb07b84394cc952d0ae20b7f29957e6555 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 17 Nov 2016 22:22:39 +0000 Subject: Tidy up text emails --- app/models/discussion.rb | 7 +++++-- app/views/notify/_note_mr_or_commit_email.text.erb | 2 +- app/views/notify/_simple_diff.text.erb | 2 +- app/views/notify/note_commit_email.text.erb | 4 +--- app/views/notify/note_merge_request_email.text.erb | 4 +--- spec/mailers/notify_spec.rb | 8 ++++---- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 9bd37fe6d89..75a85563235 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -165,18 +165,21 @@ class Discussion # Returns an array of at most 16 highlighted lines above a diff note def truncated_diff_lines(highlight: true) - initial_lines = highlight ? highlighted_diff_lines : diff_lines + lines = highlight ? highlighted_diff_lines : diff_lines prev_lines = [] - initial_lines.each do |line| + lines.each do |line| if line.meta? prev_lines.clear else prev_lines << line + break if for_line?(line) + prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES end end + prev_lines end diff --git a/app/views/notify/_note_mr_or_commit_email.text.erb b/app/views/notify/_note_mr_or_commit_email.text.erb index 3dd1b4d4c0e..b4fcdf6b1e9 100644 --- a/app/views/notify/_note_mr_or_commit_email.text.erb +++ b/app/views/notify/_note_mr_or_commit_email.text.erb @@ -1,4 +1,4 @@ -<% if @note.diff_note? && @note.diff_file -%> +<% if @discussion && @discussion.diff_file -%> on <%= @note.diff_file.file_path -%> <% end -%>: diff --git a/app/views/notify/_simple_diff.text.erb b/app/views/notify/_simple_diff.text.erb index 58b0018c0ca..c28d1cc34d3 100644 --- a/app/views/notify/_simple_diff.text.erb +++ b/app/views/notify/_simple_diff.text.erb @@ -1,3 +1,3 @@ <% @discussion.truncated_diff_lines(highlight: false).each do |line| %> - <%= "> " + line.text %> +> <%= line.text %> <% end %> diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb index dc764b61659..6aa085a172e 100644 --- a/app/views/notify/note_commit_email.text.erb +++ b/app/views/notify/note_commit_email.text.erb @@ -1,4 +1,2 @@ -<% url = url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> - New comment for Commit <%= @commit.short_id -%> -<%= render partial: 'note_mr_or_commit_email', locals: { url: url } %> +<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url } %> diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index e33d15daded..2ce64c494cf 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -1,4 +1,2 @@ -<% url = url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> - New comment for Merge Request <%= @merge_request.to_reference -%> -<%= render partial: 'note_mr_or_commit_email', locals: { url: url }%> +<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url }%> diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 76ea5f6be31..1c4ecf83141 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -50,7 +50,7 @@ describe Notify do context 'when enabled email_author_in_body' do before do - allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true) + stub_application_setting(email_author_in_body: true) end it 'contains a link to note author' do @@ -229,7 +229,7 @@ describe Notify do context 'when enabled email_author_in_body' do before do - allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true) + stub_application_setting(email_author_in_body: true) end it 'contains a link to note author' do @@ -607,7 +607,7 @@ describe Notify do context 'when enabled email_author_in_body' do before do - allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true) + stub_application_setting(email_author_in_body: true) end it 'contains a link to note author' do @@ -726,7 +726,7 @@ describe Notify do context 'when enabled email_author_in_body' do before do - allow_any_instance_of(ApplicationSetting).to receive(:email_author_in_body).and_return(true) + stub_application_setting(email_author_in_body: true) end it 'contains a link to note author' do -- cgit v1.2.1 From 93f6fcc91e5f241aeef13e96a16bbb6f4027e2fe Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 22 Nov 2016 12:46:00 +0000 Subject: Don't remove + / - signs from diff emails In the browser, we remove the + and - signs from the front of a diff line because we add them in with CSS, so they aren't copied. We can't do that in an email, because the CSS isn't supported, so we should keep them in that case. --- app/helpers/diff_helper.rb | 4 +++- app/views/projects/diffs/_line.html.haml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index f489f9aa0d6..ce16d971dc6 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -51,9 +51,11 @@ module DiffHelper html.html_safe end - def diff_line_content(line) + def diff_line_content(line, email: false) if line.blank? " ".html_safe + elsif email + line.html_safe else line.sub(/^[\-+ ]/, '').html_safe end diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index a3e4b5b777e..bf2519d4f1c 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -25,7 +25,7 @@ %a{href: "##{line_code}", data: { linenumber: link_text }} %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< - if email - %pre= diff_line_content(line.text) + %pre= diff_line_content(line.text, email: true) - else = diff_line_content(line.text) -- cgit v1.2.1 From 87a2762b8b001aa8b8d715c964f5ce99d2585ead Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 23 Nov 2016 16:21:45 +0000 Subject: Don't use diff_line_content for emails --- app/helpers/diff_helper.rb | 4 +--- app/views/projects/diffs/_line.html.haml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index ce16d971dc6..f489f9aa0d6 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -51,11 +51,9 @@ module DiffHelper html.html_safe end - def diff_line_content(line, email: false) + def diff_line_content(line) if line.blank? " ".html_safe - elsif email - line.html_safe else line.sub(/^[\-+ ]/, '').html_safe end diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index bf2519d4f1c..16c96b66714 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -25,7 +25,7 @@ %a{href: "##{line_code}", data: { linenumber: link_text }} %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< - if email - %pre= diff_line_content(line.text, email: true) + %pre= line.text - else = diff_line_content(line.text) -- cgit v1.2.1 From b8917eb75e94cb13b02534c920ee926c9e97174e Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 23 Nov 2016 16:25:31 +0000 Subject: Fix spec style --- spec/mailers/notify_spec.rb | 1 + spec/models/discussion_spec.rb | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 1c4ecf83141..39ba48f61cb 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -646,6 +646,7 @@ describe Notify do before(:each) { allow(note).to receive(:noteable).and_return(merge_request) } subject { Notify.note_merge_request_email(recipient.id, note.id) } + it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do let(:model) { merge_request } diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index 187d0bc0150..2a67c60b978 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -595,23 +595,17 @@ describe Discussion, model: true do let(:truncated_lines) { subject.truncated_diff_lines } context "when diff is greater than allowed number of truncated diff lines " do - let(:initial_line_count) { subject.diff_lines.count } - let(:truncated_line_count) { truncated_lines.count } - it "returns fewer lines" do - expect(initial_line_count).to be > described_class::NUMBER_OF_TRUNCATED_DIFF_LINES + expect(subject.diff_lines.count).to be > described_class::NUMBER_OF_TRUNCATED_DIFF_LINES - expect(truncated_line_count).to be <= described_class::NUMBER_OF_TRUNCATED_DIFF_LINES + expect(truncated_lines.count).to be <= described_class::NUMBER_OF_TRUNCATED_DIFF_LINES end end context "when some diff lines are meta" do - let(:initial_meta_lines?) { subject.diff_lines.any?(&:meta?) } - let(:truncated_meta_lines?) { truncated_lines.any?(&:meta?) } - it "returns no meta lines" do - expect(initial_meta_lines?).to be true - expect(truncated_meta_lines?).to be false + expect(subject.diff_lines).to include(be_meta) + expect(truncated_lines).not_to include(be_meta) end end end -- cgit v1.2.1 From 87665170eccfc423fc3f7fe2cadd208d808a6847 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 23 Nov 2016 16:25:37 +0000 Subject: Use assigned variables better --- app/views/discussions/_diff_with_notes.html.haml | 2 +- app/views/notify/_note_mr_or_commit_email.html.haml | 6 ++---- lib/gitlab/diff/file.rb | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 5c667e4842b..3a95a652810 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -9,7 +9,7 @@ %table - discussions = { discussion.original_line_code => discussion } = render partial: "projects/diffs/line", - collection: discussion.highlighted_diff_lines, + collection: discussion.truncated_diff_lines, as: :line, locals: { diff_file: diff_file, discussions: discussions, diff --git a/app/views/notify/_note_mr_or_commit_email.html.haml b/app/views/notify/_note_mr_or_commit_email.html.haml index 15e92c42b14..edf8dfe7e9e 100644 --- a/app/views/notify/_note_mr_or_commit_email.html.haml +++ b/app/views/notify/_note_mr_or_commit_email.html.haml @@ -3,12 +3,10 @@ New comment -- if @note.diff_note? && @note.diff_file +- if @discussion && @discussion.diff_file on = link_to @note.diff_file.file_path, @target_url, class: 'details' -\: - -- if @discussion + \: %table = render partial: "projects/diffs/line", collection: @discussion.truncated_diff_lines, diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 84784aaf2fd..c6bf25b5874 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -69,7 +69,7 @@ module Gitlab diff_refs.try(:head_sha) end - attr_writer :highlighted_diff_lines, :text_parsed_diff_lines + attr_writer :highlighted_diff_lines # Array of Gitlab::Diff::Line objects def diff_lines -- cgit v1.2.1 From 7c607a55ab339293b0e67eeb33439d5407e22aad Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 15:51:27 +0100 Subject: Grapify the projects API --- doc/api/projects.md | 1 + lib/api/helpers.rb | 26 +- lib/api/projects.rb | 578 +++++++++++++++++-------------------- spec/requests/api/projects_spec.rb | 41 ++- 4 files changed, 288 insertions(+), 358 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index de57f91bb8e..132be644b59 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -626,6 +626,7 @@ Parameters: | `path` | string | no | Custom repository name for new project. By default generated based on name | | `default_branch` | string | no | `master` by default | | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | +| `default_branch` | string | no | `master` by default | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | | `merge_requests_enabled` | boolean | no | Enable merge requests for this project | diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0d3ddb89dc3..2fd50143e91 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -324,11 +324,6 @@ module API # Projects helpers def filter_projects(projects) - # If the archived parameter is passed, limit results accordingly - if params[:archived].present? - projects = projects.where(archived: to_boolean(params[:archived])) - end - if params[:search].present? projects = projects.search(params[:search]) end @@ -337,25 +332,8 @@ module API projects = projects.search_by_visibility(params[:visibility]) end - projects.reorder(project_order_by => project_sort) - end - - def project_order_by - order_fields = %w(id name path created_at updated_at last_activity_at) - - if order_fields.include?(params['order_by']) - params['order_by'] - else - 'created_at' - end - end - - def project_sort - if params["sort"] == 'asc' - :asc - else - :desc - end + projects = projects.where(archived: params[:archived]) + projects.reorder(params[:order_by] => params[:sort]) end # file helpers diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2ea3c433ae2..8975b1a751c 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,293 +1,287 @@ module API # Projects API class Projects < Grape::API + include PaginationParams + before { authenticate! } - resource :projects, requirements: { id: /[^\/]+/ } do + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the project' + optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' + optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled' + optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled' + optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled' + optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled' + optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' + optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' + optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' + optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.' + optional :visibility_level, type: Integer, values: [ + Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.' + optional :public_builds, type: Boolean, desc: 'Perform public builds' + optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' + optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' + end + + def map_public_to_visibility_level(attrs) + publik = attrs.delete(:public) + if !publik.nil? && !attrs[:visibility_level].present? + # Since setting the public attribute to private could mean either + # private or internal, use the more conservative option, private. + attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE + end + attrs + end + end + + resource :projects do helpers do - def map_public_to_visibility_level(attrs) - publik = attrs.delete(:public) - if publik.present? && !attrs[:visibility_level].present? - publik = to_boolean(publik) - # Since setting the public attribute to private could mean either - # private or internal, use the more conservative option, private. - attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE - end - attrs + params :sort_params do + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' + end + + params :filter_params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: %w[public internal private], + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + use :sort_params + end + + params :create_params do + optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' + optional :import_url, type: String, desc: 'URL from which the project is imported' end end - # Get a projects list for authenticated user - # - # Example Request: - # GET /projects + desc 'Get a projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end get do projects = current_user.authorized_projects projects = filter_projects(projects) - projects = paginate projects entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - present projects, with: entity, user: current_user + present paginate(projects), with: entity, user: current_user end - # Get a list of visible projects for authenticated user - # - # Example Request: - # GET /projects/visible + desc 'Get a list of visible projects for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end get '/visible' do projects = ProjectsFinder.new.execute(current_user) projects = filter_projects(projects) - projects = paginate projects entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - present projects, with: entity, user: current_user + present paginate(projects), with: entity, user: current_user end - # Get an owned projects list for authenticated user - # - # Example Request: - # GET /projects/owned + desc 'Get an owned projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/owned' do projects = current_user.owned_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Gets starred project for the authenticated user - # - # Example Request: - # GET /projects/starred + desc 'Gets starred project for the authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/starred' do projects = current_user.viewable_starred_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::Project, user: current_user + + present paginate(projects), with: Entities::Project, user: current_user end - # Get all projects for admin user - # - # Example Request: - # GET /projects/all + desc 'Get all projects for admin user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/all' do authenticated_as_admin! projects = Project.all projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Get a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id - get ":id" do - present user_project, with: Entities::ProjectWithAccess, user: current_user, - user_can_admin_project: can?(current_user, :admin_project, user_project) + desc 'Search for projects the current user has access to' do + success Entities::Project + end + params do + requires :query, type: String, desc: 'The project name to be searched' + use :sort_params + use :pagination end + get "/search/:query" do + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(params[:order_by] => params[:sort]) - # Get events for a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/events - get ":id/events" do - events = paginate user_project.events.recent - present events, with: Entities::Event - end - - # Create new project - # - # Parameters: - # name (required) - name for new project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # namespace_id (optional) - defaults to user namespace - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - 0 by default - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects + present paginate(projects), with: Entities::Project + end + + desc 'Create new project' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + use :create_params + end post do - required_attributes! [:name] - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :namespace_id, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(current_user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(current_user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - if @project.errors[:limit_reached].present? - error!(@project.errors[:limit_reached], 403) + if project.errors[:limit_reached].present? + error!(project.errors[:limit_reached], 403) end - render_validation_error!(@project) + render_validation_error!(project) end end - # Create new project for a specified user. Only available to admin users. - # - # Parameters: - # user_id (required) - The ID of a user - # name (required) - name for new project - # description (optional) - short project description - # default_branch (optional) - 'master' by default - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects/user/:user_id + desc 'Create new project for a specified user. Only available to admin users.' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + requires :user_id, type: Integer, desc: 'The ID of a user' + optional :default_branch, type: String, desc: 'The default branch of the project' + use :optional_params + use :create_params + end post "user/:user_id" do authenticated_as_admin! - user = User.find(params[:user_id]) - attrs = attributes_for_keys [:builds_enabled, - :default_branch, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + user = User.find_by(id: params.delete(:user_id)) + not_found!('User') unless user + + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - render_validation_error!(@project) + render_validation_error!(project) end end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: /[^\/]+/ } do + desc 'Get a single project' do + success Entities::ProjectWithAccess + end + get ":id" do + present user_project, with: Entities::ProjectWithAccess, user: current_user, + user_can_admin_project: can?(current_user, :admin_project, user_project) + end + + desc 'Get events for a single project' do + success Entities::Event + end + params do + use :pagination + end + get ":id/events" do + present paginate(user_project.events.recent), with: Entities::Event + end - # Fork new project for the current user or provided namespace. - # - # Parameters: - # id (required) - The ID of a project - # namespace (optional) - The ID or name of the namespace that the project will be forked into. - # Example Request - # POST /projects/fork/:id + desc 'Fork new project for the current user or provided namespace.' do + success Entities::Project + end + params do + optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + end post 'fork/:id' do - attrs = {} - namespace_id = params[:namespace] + fork_params = declared_params(include_missing: false) + namespace_id = fork_params[:namespace] if namespace_id.present? - namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id) + fork_params[:namespace] = if namespace_id =~ /^\d+$/ + Namespace.find_by(id: namespace_id) + else + Namespace.find_by_path_or_name(namespace_id) + end - unless namespace && can?(current_user, :create_projects, namespace) + unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) not_found!('Target Namespace') end - - attrs[:namespace] = namespace end - @forked_project = - ::Projects::ForkService.new(user_project, - current_user, - attrs).execute + forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute - if @forked_project.errors.any? - conflict!(@forked_project.errors.messages) + if forked_project.errors.any? + conflict!(forked_project.errors.messages) else - present @forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @forked_project) + present forked_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, forked_project) end end - # Update an existing project - # - # Parameters: - # id (required) - the id of a project - # name (optional) - name of a project - # path (optional) - path of a project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - visibility level of a project - # public_builds (optional) - # lfs_enabled (optional) - # Example Request - # PUT /projects/:id + desc 'Update an existing project' do + success Entities::Project + end + params do + optional :name, type: String, desc: 'The name of the project' + optional :default_branch, type: String, desc: 'The default branch of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled, + :wiki_enabled, :builds_enabled, :snippets_enabled, + :shared_runners_enabled, :container_registry_enabled, + :lfs_enabled, :public, :visibility_level, :public_builds, + :request_access_enabled, :only_allow_merge_if_build_succeeds, + :only_allow_merge_if_all_discussions_are_resolved, :path, + :default_branch + end put ':id' do - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :default_branch, - :description, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) authorize_admin_project + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) authorize! :rename_project, user_project if attrs[:name].present? - if attrs[:visibility_level].present? - authorize! :change_visibility_level, user_project - end + authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? - ::Projects::UpdateService.new(user_project, - current_user, attrs).execute + ::Projects::UpdateService.new(user_project, current_user, attrs).execute if user_project.errors.any? render_validation_error!(user_project) @@ -297,12 +291,9 @@ module API end end - # Archive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/archive + desc 'Archive a project' do + success Entities::Project + end post ':id/archive' do authorize!(:archive_project, user_project) @@ -311,12 +302,9 @@ module API present user_project, with: Entities::Project end - # Unarchive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/unarchive + desc 'Unarchive a project' do + success Entities::Project + end post ':id/unarchive' do authorize!(:archive_project, user_project) @@ -325,12 +313,9 @@ module API present user_project, with: Entities::Project end - # Star project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # POST /projects/:id/star + desc 'Star a project' do + success Entities::Project + end post ':id/star' do if current_user.starred?(user_project) not_modified! @@ -342,12 +327,9 @@ module API end end - # Unstar project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/star + desc 'Unstar a project' do + success Entities::Project + end delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) @@ -359,67 +341,51 @@ module API end end - # Remove project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id + desc 'Remove a project' delete ":id" do authorize! :remove_project, user_project ::Projects::DestroyService.new(user_project, current_user, {}).async_execute end - # Mark this project as forked from another - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # forked_from_id: (required) - The ID of the project it was forked from - # Example Request: - # POST /projects/:id/fork/:forked_from_id + desc 'Mark this project as forked from another' + params do + requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' + end post ":id/fork/:forked_from_id" do authenticated_as_admin! + forked_from_project = find_project!(params[:forked_from_id]) - unless forked_from_project.nil? - if user_project.forked_from_project.nil? - user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) - else - render_api_error!("Project already forked", 409) - end + not_found!("Source Project") unless forked_from_project + + if user_project.forked_from_project.nil? + user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) else - not_found!("Source Project") + render_api_error!("Project already forked", 409) end end - # Remove a forked_from relationship - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # Example Request: - # DELETE /projects/:id/fork + desc 'Remove a forked_from relationship' delete ":id/fork" do authorize! :remove_fork_project, user_project + if user_project.forked? user_project.forked_project_link.destroy + else + not_modified! end end - # Share project with group - # - # Parameters: - # id (required) - The ID of a project - # group_id (required) - The ID of a group - # group_access (required) - Level of permissions for sharing - # expires_at (optional) - Share expiration date - # - # Example Request: - # POST /projects/:id/share + desc 'Share the project with a group' do + success Entities::ProjectGroupLink + end + params do + requires :group_id, type: Integer, desc: 'The ID of a group' + requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' + optional :expires_at, type: Date, desc: 'Share expiration date' + end post ":id/share" do authorize! :admin_project, user_project - required_attributes! [:group_id, :group_access] - attrs = attributes_for_keys [:group_id, :group_access, :expires_at] - - group = Group.find_by_id(attrs[:group_id]) + group = Group.find_by_id(params[:group_id]) unless group && can?(current_user, :read_group, group) not_found!('Group') @@ -429,7 +395,7 @@ module API return render_api_error!("The project sharing with group is disabled", 400) end - link = user_project.project_group_links.new(attrs) + link = user_project.project_group_links.new(declared_params(include_missing: false)) if link.save present link, with: Entities::ProjectGroupLink @@ -451,40 +417,26 @@ module API no_content! end - # Upload a file - # - # Parameters: - # id: (required) - The ID of the project - # file: (required) - The file to be uploaded + desc 'Upload a file' + params do + requires :file, type: File, desc: 'The file to be uploaded' + end post ":id/uploads" do ::Projects::UploadService.new(user_project, params[:file]).execute end - # search for projects current_user has access to - # - # Parameters: - # query (required) - A string contained in the project name - # per_page (optional) - number of projects to return per page - # page (optional) - the page to retrieve - # Example Request: - # GET /projects/search/:query - get "/search/:query" do - search_service = Search::GlobalService.new(current_user, search: params[:query]).execute - projects = search_service.objects('projects', params[:page]) - projects = projects.reorder(project_order_by => project_sort) - - present paginate(projects), with: Entities::Project + desc 'Get the users list of a project' do + success Entities::UserBasic + end + params do + optional :search, type: String, desc: 'Return list of users matching the search criteria' + use :pagination end - - # Get a users list - # - # Example Request: - # GET /users get ':id/users' do - @users = User.where(id: user_project.team.users.map(&:id)) - @users = @users.search(params[:search]) if params[:search].present? - @users = paginate @users - present @users, with: Entities::UserBasic + users = User.where(id: user_project.team.users.map(&:id)) + users = users.search(params[:search]) if params[:search].present? + + present paginate(users), with: Entities::UserBasic end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e53ee2a4e76..482e81b29a6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -415,16 +415,7 @@ describe API::API, api: true do not_to change { Project.count } expect(response).to have_http_status(400) - expect(json_response['message']['name']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.project_name_regex_message - ]) - expect(json_response['message']['path']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.send(:project_path_regex_message) - ]) + expect(json_response['error']).to eq('name is missing') end it 'assigns attributes to project' do @@ -438,6 +429,7 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) project.each_pair do |k, v| next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) @@ -447,6 +439,8 @@ describe API::API, api: true do it 'sets a project as public' do project = attributes_for(:project, :public) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -454,6 +448,8 @@ describe API::API, api: true do it 'sets a project as public using :public' do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -461,6 +457,8 @@ describe API::API, api: true do it 'sets a project as internal' do project = attributes_for(:project, :internal) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -468,6 +466,7 @@ describe API::API, api: true do it 'sets a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -848,7 +847,7 @@ describe API::API, api: true do it 'is idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(200) + expect(response).to have_http_status(304) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -865,7 +864,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at end.to change { ProjectGroupLink.count }.by(1) - expect(response.status).to eq 201 + expect(response).to have_http_status(201) expect(json_response['group_id']).to eq(group.id) expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) expect(json_response['expires_at']).to eq(expires_at.to_s) @@ -873,18 +872,18 @@ describe API::API, api: true do it "returns a 400 error when group id is not given" do post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when access level is not given" do post api("/projects/#{project.id}/share", user), group_id: group.id - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when sharing is disabled" do project.namespace.update(share_with_group_lock: true) post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it 'returns a 404 error when user cannot read group' do @@ -892,19 +891,20 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end it 'returns a 404 error when group does not exist' do post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end - it "returns a 409 error when wrong params passed" do + it "returns a 400 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 - expect(response.status).to eq 409 - expect(json_response['message']).to eq 'Group access is not included in the list' + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq 'group_access does not have a valid value' end end @@ -1017,7 +1017,6 @@ describe API::API, api: true do it 'updates visibility_level from public to private' do project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) - project_param = { public: false } put api("/projects/#{project3.id}", user), project_param expect(response).to have_http_status(200) -- cgit v1.2.1 From ac761e450919b7a2d2dd76e08afe2a877e878e61 Mon Sep 17 00:00:00 2001 From: Dan Dedrick Date: Mon, 28 Nov 2016 15:49:07 -0500 Subject: Fix broken README.md UX guide link. Replace broken link to UX guide with new working link in the README.md file. --- README.md | 2 +- changelogs/unreleased/readme-link-fix.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/readme-link-fix.yml diff --git a/README.md b/README.md index f63543ca39d..c9bb5af6da2 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ For more information please see the [architecture documentation](https://docs.gi ## UX design -Please adhere to the [UX Guide](doc/development/ux_guide/readme.md) when creating designs and implementing code. +Please adhere to the [UX Guide](doc/development/ux_guide/index.md) when creating designs and implementing code. ## Third-party applications diff --git a/changelogs/unreleased/readme-link-fix.yml b/changelogs/unreleased/readme-link-fix.yml new file mode 100644 index 00000000000..211d3b80c3a --- /dev/null +++ b/changelogs/unreleased/readme-link-fix.yml @@ -0,0 +1,4 @@ +--- +title: Fix broken README.md UX guide link. +merge_request: +author: -- cgit v1.2.1 From b834ecd2f6c3ce28e343f6c82b333a504ee125eb Mon Sep 17 00:00:00 2001 From: Chris Peressini Date: Mon, 28 Nov 2016 16:38:05 +0000 Subject: Add darker active state for outline buttons and new border colors. --- app/assets/stylesheets/framework/buttons.scss | 24 +++++++++++++++--------- app/assets/stylesheets/framework/variables.scss | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 4a9aa0f8717..2f52588dc18 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -15,7 +15,7 @@ @include btn-default; } -@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border) { +@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) { background-color: $background; color: $text; border-color: $border; @@ -23,8 +23,14 @@ &:hover, &:focus { background-color: $hover-background; - color: $hover-text; border-color: $hover-border; + color: $hover-text; + } + + &:active { + background-color: $active-background; + border-color: $active-border; + color: $hover-text; } } @@ -82,11 +88,11 @@ } @mixin btn-gray { - @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark); + @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-gray-dark); } @mixin btn-white { - @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active); + @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $gl-text-color); } @mixin btn-with-margin { @@ -139,11 +145,11 @@ &.btn-new, &.btn-create, &.btn-save { - @include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light); + @include btn-outline($white-light, $border-green-light, $border-green-light, $green-light, $white-light, $border-green-light, $green-normal, $border-green-normal); } &.btn-remove { - @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); + @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); } } @@ -165,11 +171,11 @@ } &.btn-close { - @include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light); + @include btn-outline($white-light, $border-orange-light, $border-orange-light, $orange-light, $white-light, $border-orange-light, $orange-normal, $border-orange-normal); } &.btn-spam { - @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); + @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); } &.btn-danger, @@ -351,7 +357,7 @@ .btn-inverted { &-secondary { - @include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light); + @include btn-outline($white-light, $border-blue-light, $border-blue-light, $blue-light, $white-light, $border-blue-light, $blue-normal, $border-blue-normal); } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 2539c841111..b259e7eae3e 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,7 +12,7 @@ $sidebar-breakpoint: 1024px; /* * Color schema */ - $darken-normal-factor: 7%; +$darken-normal-factor: 7%; $darken-dark-factor: 10%; $darken-border-factor: 5%; -- cgit v1.2.1 From b62e2bedbfa49aacfc4847049aa589f045af15ce Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Mon, 28 Nov 2016 17:00:03 -0500 Subject: Add new configuration setting to enable/disable HTML emails. This new global setting will allow admins to specify if HTML emails should be sent or not, this is basically useful when system administrators want to save some disk space by avoiding emails in HTML format and using only the Plain Text version. --- .../admin/application_settings_controller.rb | 1 + .../admin/application_settings/_form.html.haml | 11 ++++++- .../7749-add-setting-to-disable-html-emails.yml | 3 ++ config/initializers/email_template_interceptor.rb | 2 ++ ..._html_emails_enabled_to_application_settings.rb | 29 ++++++++++++++++++ db/schema.rb | 3 +- lib/email_template_interceptor.rb | 13 +++++++++ spec/mailers/notify_spec.rb | 34 ++++++++++++++++++++++ 8 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/7749-add-setting-to-disable-html-emails.yml create mode 100644 config/initializers/email_template_interceptor.rb create mode 100644 db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb create mode 100644 lib/email_template_interceptor.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b81842e319b..c2bb8464824 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -112,6 +112,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :koding_enabled, :koding_url, :email_author_in_body, + :html_emails_enabled, :repository_checks_enabled, :metrics_packet_size, :send_user_confirmation_email, diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index ce803f329f9..7accd2529af 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -443,7 +443,16 @@ Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. - + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :html_emails_enabled do + = f.check_box :html_emails_enabled + Enable HTML emails + .help-block + By default GitLab sends emails in HTML and plain text formats so mail + clients can choose what format to use. Disable this option if you only + want to send emails in plain text format. %fieldset %legend Automatic Git repository housekeeping .form-group diff --git a/changelogs/unreleased/7749-add-setting-to-disable-html-emails.yml b/changelogs/unreleased/7749-add-setting-to-disable-html-emails.yml new file mode 100644 index 00000000000..9dd04d3f089 --- /dev/null +++ b/changelogs/unreleased/7749-add-setting-to-disable-html-emails.yml @@ -0,0 +1,3 @@ +title: Add setting to enable/disable HTML emails +merge_request: 7749 +author: diff --git a/config/initializers/email_template_interceptor.rb b/config/initializers/email_template_interceptor.rb new file mode 100644 index 00000000000..f195ca9bcd6 --- /dev/null +++ b/config/initializers/email_template_interceptor.rb @@ -0,0 +1,2 @@ +# Interceptor in lib/email_template_interceptor.rb +ActionMailer::Base.register_interceptor(EmailTemplateInterceptor) diff --git a/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb new file mode 100644 index 00000000000..1c59241d0fe --- /dev/null +++ b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddHtmlEmailsEnabledToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :application_settings, :html_emails_enabled, :boolean, default: true + end +end diff --git a/db/schema.rb b/db/schema.rb index b3c49b52597..3d630a148f0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161118183841) do +ActiveRecord::Schema.define(version: 20161128161412) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -106,6 +106,7 @@ ActiveRecord::Schema.define(version: 20161118183841) do t.integer "housekeeping_incremental_repack_period", default: 10, null: false t.integer "housekeeping_full_repack_period", default: 50, null: false t.integer "housekeeping_gc_period", default: 200, null: false + t.boolean "html_emails_enabled", default: true end create_table "audit_events", force: :cascade do |t| diff --git a/lib/email_template_interceptor.rb b/lib/email_template_interceptor.rb new file mode 100644 index 00000000000..fb04a7824b8 --- /dev/null +++ b/lib/email_template_interceptor.rb @@ -0,0 +1,13 @@ +# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails +class EmailTemplateInterceptor + include Gitlab::CurrentSettings + + def self.delivering_email(message) + # Remove HTML part if HTML emails are disabled. + unless current_application_settings.html_emails_enabled + message.part.delete_if do |part| + part.content_type.try(:start_with?, 'text/html') + end + end + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 932a5dc4862..0ad1cefbacc 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -1099,4 +1099,38 @@ describe Notify do is_expected.to have_body_text /#{diff_path}/ end end + + describe 'HTML emails setting' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:multipart_mail) { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } + + context 'when disabled' do + it 'only sends the text template' do + stub_application_setting(html_emails_enabled: false) + + EmailTemplateInterceptor.delivering_email(multipart_mail) + + expect(multipart_mail).to have_part_with('text/plain') + expect(multipart_mail).not_to have_part_with('text/html') + end + end + + context 'when enabled' do + it 'sends a multipart message' do + stub_application_setting(html_emails_enabled: true) + + EmailTemplateInterceptor.delivering_email(multipart_mail) + + expect(multipart_mail).to have_part_with('text/plain') + expect(multipart_mail).to have_part_with('text/html') + end + end + + matcher :have_part_with do |expected| + match do |actual| + actual.body.parts.any? { |part| part.content_type.try(:match, %r(#{expected})) } + end + end + end end -- cgit v1.2.1 From 59fa98dd8462fa2f7865828275b5e80e362e4a6e Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Tue, 29 Nov 2016 01:13:54 +0300 Subject: Enable ESLint and fix minor code style stuff in project_variables.js.es6. --- app/assets/javascripts/project_variables.js.es6 | 41 ++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/project_variables.js.es6 b/app/assets/javascripts/project_variables.js.es6 index 6c905f58c85..4ee2e49306d 100644 --- a/app/assets/javascripts/project_variables.js.es6 +++ b/app/assets/javascripts/project_variables.js.es6 @@ -1,44 +1,43 @@ -/* eslint-disable */ -((global) => { +(() => { const HIDDEN_VALUE_TEXT = '******'; class ProjectVariables { constructor() { - this.$reveal = $('.js-btn-toggle-reveal-values'); - - this.$reveal.on('click', this.toggleRevealState.bind(this)); + this.$revealBtn = $('.js-btn-toggle-reveal-values'); + this.$revealBtn.on('click', this.toggleRevealState.bind(this)); } - toggleRevealState(event) { - event.preventDefault(); + toggleRevealState(e) { + e.preventDefault(); - const $btn = $(event.currentTarget); - const oldStatus = $btn.attr('data-status'); + const oldStatus = this.$revealBtn.attr('data-status'); + let newStatus = 'hidden'; + let newAction = 'Reveal Values'; - if (oldStatus == 'hidden') { - [newStatus, newAction] = ['revealed', 'Hide Values']; - } else { - [newStatus, newAction] = ['hidden', 'Reveal Values']; + if (oldStatus === 'hidden') { + newStatus = 'revealed'; + newAction = 'Hide Values'; } - $btn.attr('data-status', newStatus); + this.$revealBtn.attr('data-status', newStatus); - let $variables = $('.variable-value'); + const $variables = $('.variable-value'); - $variables.each(function (_, variable) { - let $variable = $(variable); + $variables.each((_, variable) => { + const $variable = $(variable); let newText = HIDDEN_VALUE_TEXT; - if (newStatus == 'revealed') { + if (newStatus === 'revealed') { newText = $variable.attr('data-value'); } $variable.text(newText); }); - $btn.text(newAction); + this.$revealBtn.text(newAction); } } - global.ProjectVariables = ProjectVariables; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + window.gl.ProjectVariables = ProjectVariables; +})(); -- cgit v1.2.1 From b3ed4e0cf9dc66b49fba933455212d9c89b80d90 Mon Sep 17 00:00:00 2001 From: David Wagner Date: Fri, 18 Nov 2016 21:19:44 +0100 Subject: Homogenize dropdowns on Issue page Make sort and filter dropdowns look the same and tweak their icon and colors according to #24150. Signed-off-by: David Wagner --- app/assets/stylesheets/framework/buttons.scss | 2 +- app/assets/stylesheets/framework/dropdowns.scss | 87 ++++++++++++++-------- app/assets/stylesheets/framework/variables.scss | 12 +-- app/helpers/dropdowns_helper.rb | 2 +- app/views/shared/_sort_dropdown.html.haml | 4 +- .../shared/issuable/_label_dropdown.html.haml | 2 +- features/steps/shared/issuable.rb | 6 +- 7 files changed, 72 insertions(+), 43 deletions(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 4a9aa0f8717..ffebef559c2 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -199,7 +199,7 @@ } .fa-caret-down, - .fa-caret-up { + .fa-chevron-down { margin-left: 5px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 583c17e4a83..d7df1d91afc 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -21,51 +21,23 @@ .dropdown-menu-toggle { border-color: $dropdown-toggle-hover-border-color; - - .fa { - color: $dropdown-toggle-hover-icon-color; - } } } -.dropdown-menu-toggle { - position: relative; - width: 160px; - padding: 6px 20px 6px 10px; +.dropdown-toggle { + padding: 6px 8px 6px 10px; background-color: $dropdown-toggle-bg; color: $dropdown-toggle-color; font-size: 15px; text-align: left; border: 1px solid $border-color; border-radius: $border-radius-base; - text-overflow: ellipsis; white-space: nowrap; - overflow: hidden; - - .fa { - position: absolute; - top: 10px; - right: 8px; - color: $dropdown-toggle-icon-color; - - &.fa-spinner { - font-size: 16px; - margin-top: -8px; - } - } &.no-outline { outline: 0; } - &:hover, { - border-color: $dropdown-toggle-hover-border-color; - - .fa { - color: $dropdown-toggle-hover-icon-color; - } - } - &.large { width: 200px; } @@ -86,6 +58,61 @@ max-width: 100%; padding-right: 25px; } + + .fa { + color: $dropdown-toggle-icon-color; + } + + .fa-chevron-down { + font-size: $dropdown-chevron-size; + position: relative; + top: -3px; + margin-left: 5px; + } + + @mixin chevron-hover { + .fa-chevron-down { + color: $dropdown-toggle-hover-icon-color; + } + } + + &:hover { + @include chevron-hover; + + border-color: $dropdown-toggle-hover-border-color; + } + + &:focus:active { + @include chevron-hover; + + border-color: $dropdown-toggle-active-border-color; + } +} + +.dropdown-menu-toggle { + @extend .dropdown-toggle; + + padding-right: 20px; + + position: relative; + width: 160px; + text-overflow: ellipsis; + overflow: hidden; + + .fa { + position: absolute; + + &.fa-spinner { + font-size: 16px; + margin-top: -8px; + } + } + + .fa-chevron-down { + position: absolute; + top: 11px; + right: 8px; + } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 750d99ebabe..88d6c3570c5 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -216,7 +216,7 @@ $dropdown-bg: #fff; $dropdown-link-color: #555; $dropdown-link-hover-bg: $row-hover; $dropdown-empty-row-bg: rgba(#000, .04); -$dropdown-border-color: rgba(#000, .1); +$dropdown-border-color: $border-color; $dropdown-shadow-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1); $dropdown-header-color: #959494; @@ -225,13 +225,15 @@ $dropdown-input-color: #555; $dropdown-input-focus-border: $focus-border-color; $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-loading-bg: rgba(#fff, .6); +$dropdown-chevron-size: 10px; $dropdown-toggle-bg: #fff; -$dropdown-toggle-color: #626262; -$dropdown-toggle-border-color: #eaeaea; -$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); +$dropdown-toggle-color: #5c5c5c; +$dropdown-toggle-border-color: #e5e5e5; +$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 13%); +$dropdown-toggle-active-border-color: darken($dropdown-toggle-border-color, 14%); $dropdown-toggle-icon-color: #c4c4c4; -$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; +$dropdown-toggle-hover-icon-color: darken($dropdown-toggle-icon-color, 7%); /* * Buttons diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index cbab1fd5967..81e0b6bb5ae 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -43,7 +43,7 @@ module DropdownsHelper default_label = data_attr[:default_label] content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") - output << icon('caret-down') + output << icon('chevron-down') output.html_safe end end diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 68e05cb72e1..ede3c7090d7 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,11 +1,11 @@ .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', data: {toggle: 'dropdown'}} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to page_filter_path(sort: sort_value_priority, label: true) do diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 1d778bc88de..22b5a6aa11b 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -22,7 +22,7 @@ %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } = multi_label_name(selected, "Labels") - = icon('caret-down') + = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } - if show_create && project && can?(current_user, :admin_label, project) diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index aa666a954bc..79dde620265 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -110,14 +110,14 @@ module SharedIssuable end step 'I sort the list by "Oldest updated"' do - find('button.dropdown-toggle.btn').click + find('button.dropdown-toggle').click page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do click_link "Oldest updated" end end step 'I sort the list by "Least popular"' do - find('button.dropdown-toggle.btn').click + find('button.dropdown-toggle').click page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do click_link 'Least popular' @@ -125,7 +125,7 @@ module SharedIssuable end step 'I sort the list by "Most popular"' do - find('button.dropdown-toggle.btn').click + find('button.dropdown-toggle').click page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do click_link 'Most popular' -- cgit v1.2.1 From 4c6468aa1077eec8e953734f410ecff680f17caf Mon Sep 17 00:00:00 2001 From: David Wagner Date: Tue, 22 Nov 2016 20:33:52 +0100 Subject: Make open and hovered dropdown toggles look the same The chevron now has the same darker shade when the dropdown is opened it had when hovered on. Signed-off-by: David Wagner --- app/assets/stylesheets/framework/dropdowns.scss | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index d7df1d91afc..de6e154dfe6 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -8,6 +8,12 @@ } } +@mixin chevron-active { + .fa-chevron-down { + color: $dropdown-toggle-hover-icon-color; + } +} + .open { .dropdown-menu, .dropdown-menu-nav { @@ -19,7 +25,10 @@ } } + .dropdown-toggle, .dropdown-menu-toggle { + @include chevron-active; + border-color: $dropdown-toggle-hover-border-color; } } @@ -70,20 +79,14 @@ margin-left: 5px; } - @mixin chevron-hover { - .fa-chevron-down { - color: $dropdown-toggle-hover-icon-color; - } - } - &:hover { - @include chevron-hover; + @include chevron-active; border-color: $dropdown-toggle-hover-border-color; } &:focus:active { - @include chevron-hover; + @include chevron-active; border-color: $dropdown-toggle-active-border-color; } -- cgit v1.2.1 From a3a85c07aa87148f4baead1ac8d2f2679136c69a Mon Sep 17 00:00:00 2001 From: David Wagner Date: Tue, 22 Nov 2016 20:56:21 +0100 Subject: Update the Changelog [ci skip] Signed-off-by: David Wagner --- changelogs/unreleased/24150-consistent-dropdown-styles.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/24150-consistent-dropdown-styles.yml diff --git a/changelogs/unreleased/24150-consistent-dropdown-styles.yml b/changelogs/unreleased/24150-consistent-dropdown-styles.yml new file mode 100644 index 00000000000..a328d796c43 --- /dev/null +++ b/changelogs/unreleased/24150-consistent-dropdown-styles.yml @@ -0,0 +1,4 @@ +--- +title: Homogenize filter and sort dropdown look'n'feel +merge_request: 7583 +author: David Wagner -- cgit v1.2.1 From be0fb391d32bb549d57841009e2dd2bb92de8a78 Mon Sep 17 00:00:00 2001 From: David Wagner Date: Tue, 22 Nov 2016 22:28:07 +0100 Subject: Update some more sort/filter dropdowns Apart from Issues and Merge Requests pages, there are other sort/filter dropdowns that needed updating. Signed-off-by: David Wagner --- app/views/dashboard/todos/index.html.haml | 4 ++-- app/views/explore/groups/index.html.haml | 4 ++-- app/views/explore/projects/_filter.html.haml | 8 ++++---- app/views/projects/branches/index.html.haml | 4 ++-- app/views/projects/builds/_sidebar.html.haml | 2 +- app/views/projects/forks/index.html.haml | 4 ++-- app/views/projects/tags/index.html.haml | 4 ++-- app/views/search/_filter.html.haml | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 472d698486b..62f52086be4 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -50,13 +50,13 @@ data: { data: todo_actions_options }}) .pull-right .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to todos_filter_path(sort: sort_value_priority) do diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index a1b39d9e1a0..4e5d965ccbe 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -17,13 +17,13 @@ .pull-right .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to explore_groups_path(sort: sort_value_recently_created) do diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 4cff14b096b..5ea154c36b4 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -1,13 +1,13 @@ - if current_user .dropdown - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('globe') %span.light Visibility: - if params[:visibility_level].present? = visibility_level_label(params[:visibility_level].to_i) - else Any - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(visibility_level: nil) do @@ -20,14 +20,14 @@ - if @tags.present? .dropdown - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('tags') %span.light Tags: - if params[:tag].present? = params[:tag] - else Any - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(tag: nil) do diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 2246316b540..5fd664c7a93 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -12,10 +12,10 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light = projects_sort_options_hash[@sort] - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_branches_path(sort: sort_value_name) do diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index f5562046953..d5004f6a066 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -116,7 +116,7 @@ .title Stage %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.stage-selection More - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu - @build.pipeline.stages.each do |stage| %li diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index abf4f697f86..5ee3979c7e7 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -9,13 +9,13 @@ spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } .dropdown - %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id] diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index b43b13de4ca..1d39f3a7534 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -12,10 +12,10 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline - %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown'} } %span.light = projects_sort_options_hash[@sort] - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_tags_path(sort: sort_value_name) do diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index ef1c0296d49..938be20c7cf 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -3,7 +3,7 @@ - if params[:project_id].present? = hidden_field_tag :project_id, params[:project_id] .dropdown - %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } } + %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } } %span.dropdown-toggle-text Group: - if @group.present? @@ -18,7 +18,7 @@ = dropdown_loading .dropdown.project-filter - %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } } + %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } } %span.dropdown-toggle-text Project: - if @project.present? -- cgit v1.2.1 From 927042985fbfeeb1a348c8772e5a00084146bc87 Mon Sep 17 00:00:00 2001 From: David Wagner Date: Wed, 23 Nov 2016 18:52:38 +0100 Subject: dropdowns.scss: Fix style issues after review Signed-off-by: David Wagner --- app/assets/stylesheets/framework/dropdowns.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index de6e154dfe6..6d77aadd753 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -28,7 +28,6 @@ .dropdown-toggle, .dropdown-menu-toggle { @include chevron-active; - border-color: $dropdown-toggle-hover-border-color; } } @@ -81,22 +80,18 @@ &:hover { @include chevron-active; - border-color: $dropdown-toggle-hover-border-color; } &:focus:active { @include chevron-active; - border-color: $dropdown-toggle-active-border-color; } } .dropdown-menu-toggle { @extend .dropdown-toggle; - padding-right: 20px; - position: relative; width: 160px; text-overflow: ellipsis; -- cgit v1.2.1 From 7de6aee7556d53ec1b327bd6d0cb40fbd3848592 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Tue, 29 Nov 2016 00:08:11 +0100 Subject: Fix pipelines info being hidden in merge request widget We do need these "ci-#{status}" classes because we use them in MergeRequestWidget to show correct divs. --- app/views/projects/merge_requests/widget/_heading.html.haml | 2 +- changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 18c72ed875c..6d9b91ad0e7 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,7 +1,7 @@ - if @pipeline .mr-widget-heading - %w[success success_with_warnings skipped canceled failed running pending].each do |status| - .ci_widget{ class: "ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } + .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } = ci_icon_for_status(status) %span Pipeline diff --git a/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml b/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml new file mode 100644 index 00000000000..dad9db0ffef --- /dev/null +++ b/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml @@ -0,0 +1,4 @@ +--- +title: Fix pipelines info being hidden in merge request widget +merge_request: 7808 +author: -- cgit v1.2.1 From cb9475259dfdbd28c6297d518b9263feb0711b93 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Mon, 28 Nov 2016 23:43:37 +0000 Subject: Remove `memberOf` OID in LDAP `user_filter` docs While not technically invalid, it is not necessary to have the `memberOf` OID in the `user_filter`. It clutters things up and causes confusion for users so it's better if we remove it from the docs. --- doc/administration/auth/ldap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index d3f216fb3bf..b8b63df091e 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -221,7 +221,7 @@ Tip: If you want to limit access to the nested members of an Active Directory group you can use the following syntax: ``` -(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com) +(memberOf=CN=My Group,DC=Example,DC=com) ``` Please note that GitLab does not support the custom filter syntax used by -- cgit v1.2.1 From 3d7704ae5f62446b8b399c796c64d1f527666376 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 10 Nov 2016 10:23:44 +0000 Subject: Merge branch 'zj-fix-label-creation-non-members' into 'security' Fix label creation non members Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/23416 See merge request !2006 --- app/services/issuable_base_service.rb | 8 ++- app/services/labels/find_or_create_service.rb | 7 +- .../zj-fix-label-creation-non-members.yml | 4 ++ lib/api/helpers.rb | 14 ---- lib/api/issues.rb | 74 +++++++++++----------- lib/api/merge_requests.rb | 10 --- spec/requests/api/issues_spec.rb | 14 +++- spec/requests/api/merge_requests_spec.rb | 27 ++++---- spec/services/labels/transfer_service_spec.rb | 2 +- 9 files changed, 78 insertions(+), 82 deletions(-) create mode 100644 changelogs/unreleased/zj-fix-label-creation-non-members.yml diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index d698b295e6d..ce68e433ab8 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -85,14 +85,15 @@ class IssuableBaseService < BaseService def find_or_create_label_ids labels = params.delete(:labels) + return unless labels - params[:label_ids] = labels.split(',').map do |label_name| + params[:label_ids] = labels.split(",").map do |label_name| service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip) label = service.execute - label.id - end + label.try(:id) + end.compact end def process_label_ids(attributes, existing_label_ids: nil) @@ -140,6 +141,7 @@ class IssuableBaseService < BaseService params.delete(:state_event) params[:author] ||= current_user + label_ids = process_label_ids(params) issuable.assign_attributes(params) diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb index d622f9edd33..cf4f7606c94 100644 --- a/app/services/labels/find_or_create_service.rb +++ b/app/services/labels/find_or_create_service.rb @@ -22,9 +22,14 @@ module Labels ).execute(skip_authorization: skip_authorization) end + # Only creates the label if current_user can do so, if the label does not exist + # and the user can not create the label, nil is returned def find_or_create_label new_label = available_labels.find_by(title: title) - new_label ||= project.labels.create(params) + + if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, project)) + new_label = project.labels.create(params) + end new_label end diff --git a/changelogs/unreleased/zj-fix-label-creation-non-members.yml b/changelogs/unreleased/zj-fix-label-creation-non-members.yml new file mode 100644 index 00000000000..ae4824f82fa --- /dev/null +++ b/changelogs/unreleased/zj-fix-label-creation-non-members.yml @@ -0,0 +1,4 @@ +--- +title: Non members cannot create labels through the API +merge_request: +author: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0d3ddb89dc3..79a83496eee 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -198,20 +198,6 @@ module API ActionController::Parameters.new(attrs).permit! end - # Helper method for validating all labels against its names - def validate_label_params(params) - errors = {} - - params[:labels].to_s.split(',').each do |label_name| - label = available_labels.find_or_initialize_by(title: label_name.strip) - next if label.valid? - - errors[label.title] = label.errors - end - - errors - end - # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601 # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked. # diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 2fea71870b8..029be7519f5 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -19,6 +19,15 @@ module API def filter_issues_milestone(issues, milestone) issues.includes(:milestone).where('milestones.title' => milestone) end + + def issue_params + new_params = declared(params, include_parent_namespace: false, include_missing: false).to_h + new_params = new_params.with_indifferent_access + new_params.delete(:id) + new_params.delete(:issue_id) + + new_params + end end resource :issues do @@ -86,6 +95,10 @@ module API end end + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects do # Get a list of project issues # @@ -152,17 +165,10 @@ module API post ':id/issues' do required_attributes! [:title] - keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential] + keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential, :labels] keys << :created_at if current_user.admin? || user_project.owner == current_user attrs = attributes_for_keys(keys) - # Validate label names in advance - if (errors = validate_label_params(params)).any? - render_api_error!({ labels: errors }, 400) - end - - attrs[:labels] = params[:labels] if params[:labels] - # Convert and filter out invalid confidential flags attrs['confidential'] = to_boolean(attrs['confidential']) attrs.delete('confidential') if attrs['confidential'].nil? @@ -180,41 +186,35 @@ module API end end - # Update an existing issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # title (optional) - The title of an issue - # description (optional) - The description of an issue - # assignee_id (optional) - The ID of a user to assign issue - # milestone_id (optional) - The ID of a milestone to assign issue - # labels (optional) - The labels of an issue - # state_event (optional) - The state event of an issue (close|reopen) - # updated_at (optional) - Date time string, ISO 8601 formatted - # due_date (optional) - Date time string in the format YEAR-MONTH-DAY - # confidential (optional) - Boolean parameter if the issue should be confidential - # Example Request: - # PUT /projects/:id/issues/:issue_id + desc 'Update an existing issue' do + success Entities::Issue + end + params do + requires :id, type: String, desc: 'The ID of a project' + requires :issue_id, type: Integer, desc: "The ID of a project issue" + optional :title, type: String, desc: 'The new title of the issue' + optional :description, type: String, desc: 'The description of an issue' + optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue' + optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' + optional :labels, type: String, desc: 'The labels of an issue' + optional :state_event, type: String, values: ['close', 'reopen'], desc: 'The state event of an issue' + # TODO 9.0, use the Grape DateTime type here + optional :updated_at, type: String, desc: 'Date time string, ISO 8601 formatted' + optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY' + # TODO 9.0, use the Grape boolean type here + optional :confidential, type: String, desc: 'Boolean parameter if the issue should be confidential' + end put ':id/issues/:issue_id' do issue = user_project.issues.find(params[:issue_id]) authorize! :update_issue, issue - keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date, :confidential] - keys << :updated_at if current_user.admin? || user_project.owner == current_user - attrs = attributes_for_keys(keys) - - # Validate label names in advance - if (errors = validate_label_params(params)).any? - render_api_error!({ labels: errors }, 400) - end - - attrs[:labels] = params[:labels] if params[:labels] # Convert and filter out invalid confidential flags - attrs['confidential'] = to_boolean(attrs['confidential']) - attrs.delete('confidential') if attrs['confidential'].nil? + params[:confidential] = to_boolean(params[:confidential]) + params.delete(:confidential) if params[:confidential].nil? + + params.delete(:updated_at) unless current_user.admin? || user_project.owner == current_user - issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) + issue = ::Issues::UpdateService.new(user_project, current_user, issue_params).execute(issue) if issue.valid? present issue, with: Entities::Issue, current_user: current_user, project: user_project diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index e82651a1578..90fa588b455 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -77,11 +77,6 @@ module API mr_params = declared_params - # Validate label names in advance - if (errors = validate_label_params(mr_params)).any? - render_api_error!({ labels: errors }, 400) - end - merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute if merge_request.valid? @@ -157,11 +152,6 @@ module API mr_params = declared_params(include_missing: false) - # Validate label names in advance - if (errors = validate_label_params(mr_params)).any? - render_api_error!({ labels: errors }, 400) - end - merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) if merge_request.valid? diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 7bae055b241..b17553211d2 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -697,6 +697,14 @@ describe API::API, api: true do expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end + + context 'the user can only read the issue' do + it 'cannot create new labels' do + expect do + post api("/projects/#{project.id}/issues", non_member), title: 'new issue', labels: 'label, label2' + end.not_to change { project.labels.count } + end + end end describe 'POST /projects/:id/issues with spam filtering' do @@ -839,8 +847,8 @@ describe API::API, api: true do end it 'removes all labels' do - put api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: '' + put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' + expect(response).to have_http_status(200) expect(json_response['labels']).to eq([]) end @@ -892,8 +900,8 @@ describe API::API, api: true do update_time = 2.weeks.ago put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label3', state_event: 'close', updated_at: update_time - expect(response).to have_http_status(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label3' expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 37fcb2bc3a9..3ecf3eea5f5 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -402,14 +402,6 @@ describe API::API, api: true do end end - describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do - it "returns merge_request" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" - expect(response).to have_http_status(200) - expect(json_response['state']).to eq('closed') - end - end - describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do let(:pipeline) { create(:ci_pipeline_without_jobs) } @@ -486,6 +478,15 @@ describe API::API, api: true do end describe "PUT /projects/:id/merge_requests/:merge_request_id" do + context "to close a MR" do + it "returns merge_request" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" + + expect(response).to have_http_status(200) + expect(json_response['state']).to eq('closed') + end + end + it "updates title and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" expect(response).to have_http_status(200) @@ -511,10 +512,10 @@ describe API::API, api: true do end it 'allows special label names' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", - user), - title: 'new issue', - labels: 'label, label?, label&foo, ?, &' + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), + title: 'new issue', + labels: 'label, label?, label&foo, ?, &' + expect(response.status).to eq(200) expect(json_response['labels']).to include 'label' expect(json_response['labels']).to include 'label?' @@ -543,7 +544,7 @@ describe API::API, api: true do it "returns 404 if note is attached to non existent merge request" do post api("/projects/#{project.id}/merge_requests/404/comments", user), - note: 'My comment' + note: 'My comment' expect(response).to have_http_status(404) end end diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index ddf3527dc0f..13654a0881c 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Labels::TransferService, services: true do describe '#execute' do - let(:user) { create(:user) } + let(:user) { create(:admin) } let(:group_1) { create(:group) } let(:group_2) { create(:group) } let(:group_3) { create(:group) } -- cgit v1.2.1 From 742cee756bf39d93fe5c7f207f8a54143ae6a384 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 7 Nov 2016 17:09:22 +0000 Subject: Merge branch 'jej-22869' into 'security' Fix information disclosure in `Projects::BlobController#update` It was possible to discover private project names by modifying `from_merge_request`parameter in `Projects::BlobController#update`. This fixes that. - [ ] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG.md) entry added - Tests - [x] Added for this feature/bug - [ ] All builds are passing - [x] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html) - [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides) - [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) https://gitlab.com/gitlab-org/gitlab-ce/issues/22869 See merge request !2023 --- app/controllers/projects/blob_controller.rb | 20 ++++----- app/views/projects/blob/edit.html.haml | 2 +- app/views/projects/diffs/_file.html.haml | 2 +- changelogs/unreleased/jej-22869.yml | 4 ++ spec/controllers/projects/blob_controller_spec.rb | 49 +++++++++++++++++++++++ spec/features/projects/blobs/edit_spec.rb | 45 +++++++++++++++++++++ 6 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 changelogs/unreleased/jej-22869.yml create mode 100644 spec/features/projects/blobs/edit_spec.rb diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 56ced786311..9940263ae24 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -13,7 +13,6 @@ class Projects::BlobController < Projects::ApplicationController before_action :assign_blob_vars before_action :commit, except: [:new, :create] before_action :blob, except: [:new, :create] - before_action :from_merge_request, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] before_action :validate_diff_params, only: :diff @@ -39,14 +38,6 @@ class Projects::BlobController < Projects::ApplicationController def update @path = params[:file_path] if params[:file_path].present? - after_edit_path = - if from_merge_request && @target_branch == @ref - diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + - "##{hexdigest(@path)}" - else - namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) - end - create_commit(Files::UpdateService, success_path: after_edit_path, failure_view: :edit, failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) @@ -124,9 +115,14 @@ class Projects::BlobController < Projects::ApplicationController render_404 end - def from_merge_request - # If blob edit was initiated from merge request page - @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) + def after_edit_path + from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) + if from_merge_request && @target_branch == @ref + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + + "##{hexdigest(@path)}" + else + namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) + end end def editor_variables diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 2a0352a71b7..a5dcd93f42e 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -27,5 +27,5 @@ = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit_sha', @last_commit_sha = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 120ba9ffcd2..6c33d80becd 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -9,7 +9,7 @@ = icon('comment') \ - if editable_diff?(diff_file) - - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {} + - link_opts = @merge_request.persisted? ? { from_merge_request_iid: @merge_request.iid } : {} = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, blob: blob, link_opts: link_opts) diff --git a/changelogs/unreleased/jej-22869.yml b/changelogs/unreleased/jej-22869.yml new file mode 100644 index 00000000000..9d2edcfee42 --- /dev/null +++ b/changelogs/unreleased/jej-22869.yml @@ -0,0 +1,4 @@ +--- +title: Fix information disclosure in `Projects::BlobController#update` +merge_request: +author: diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 52d13fb6f9e..1c2b0a4a45c 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -36,4 +36,53 @@ describe Projects::BlobController do end end end + + describe 'PUT update' do + let(:default_params) do + { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: 'master/CHANGELOG', + target_branch: 'master', + content: 'Added changes', + commit_message: 'Update CHANGELOG' + } + end + + def blob_after_edit_path + namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG') + end + + it 'redirects to blob' do + put :update, default_params + + expect(response).to redirect_to(blob_after_edit_path) + end + + context '?from_merge_request_iid' do + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:mr_params) { default_params.merge(from_merge_request_iid: merge_request.iid) } + + it 'redirects to MR diff' do + put :update, mr_params + + after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + file_anchor = "#file-path-#{Digest::SHA1.hexdigest('CHANGELOG')}" + expect(response).to redirect_to(after_edit_path + file_anchor) + end + + context "when user doesn't have access" do + before do + other_project = create(:empty_project) + merge_request.update!(source_project: other_project, target_project: other_project) + end + + it "it redirect to blob" do + put :update, mr_params + + expect(response).to redirect_to(blob_after_edit_path) + end + end + end + end end diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb new file mode 100644 index 00000000000..a820d07ab3b --- /dev/null +++ b/spec/features/projects/blobs/edit_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +feature 'Editing file blob', feature: true, js: true do + include WaitForAjax + + given(:user) { create(:user) } + given(:role) { :developer } + given(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') } + given(:project) { merge_request.target_project } + + background do + login_as(user) + project.team << [user, role] + end + + def edit_and_commit + wait_for_ajax + first('.file-actions').click_link 'Edit' + execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")') + click_button 'Commit Changes' + end + + context 'from MR diff' do + before do + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + edit_and_commit + end + + scenario 'returns me to the mr' do + expect(page).to have_content(merge_request.title) + end + end + + context 'from blob file path' do + before do + visit namespace_project_blob_path(project.namespace, project, '/feature/files/ruby/feature.rb') + edit_and_commit + end + + scenario 'updates content' do + expect(page).to have_content 'successfully committed' + expect(page).to have_content 'NextFeature' + end + end +end -- cgit v1.2.1 From 6d37fe952b5679d7586eaa569d0488dbb92032fe Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 18 Nov 2016 13:51:52 +0000 Subject: Merge branch 'jej-fix-missing-access-check-on-issues' into 'security' Fix missing access checks on issue lookup using IssuableFinder Split from !2024 to partially solve https://gitlab.com/gitlab-org/gitlab-ce/issues/23867 :warning: - Potentially untested :bomb: - No test coverage :traffic_light: - Test coverage of some sort exists (a test failed when error raised) :vertical_traffic_light: - Test coverage of return value (a test failed when nil used) :white_check_mark: - Permissions check tested - [x] :white_check_mark: app/controllers/projects/branches_controller.rb:39 - `before_action :authorize_push_code!` helpes limit/prevent exploitation. Always checks for reporter access so fine with confidential issues, issues only visible to team, etc. - [x] :traffic_light: app/models/cycle_analytics/summary.rb:9 [`.count`] - [x] :white_check_mark: app/controllers/projects/todos_controller.rb:19 - [x] Potential double render in app/controllers/projects/todos_controller.rb - https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2024/diffs#cedccb227af9bfdf88802767cb58d43c2b977439_24_24 See merge request !2030 --- app/controllers/projects/branches_controller.rb | 2 +- app/controllers/projects/cycle_analytics_controller.rb | 2 +- app/controllers/projects/todos_controller.rb | 8 +------- app/finders/issuable_finder.rb | 8 ++++++++ app/models/cycle_analytics.rb | 5 +++-- app/models/cycle_analytics/summary.rb | 5 +++-- .../jej-fix-missing-access-check-on-issues.yml | 4 ++++ spec/controllers/projects/branches_controller_spec.rb | 18 ++++++++++++++++++ spec/controllers/projects/todo_controller_spec.rb | 17 +++++++++++++++-- spec/models/cycle_analytics/code_spec.rb | 2 +- spec/models/cycle_analytics/issue_spec.rb | 2 +- spec/models/cycle_analytics/plan_spec.rb | 2 +- spec/models/cycle_analytics/production_spec.rb | 2 +- spec/models/cycle_analytics/review_spec.rb | 2 +- spec/models/cycle_analytics/staging_spec.rb | 2 +- spec/models/cycle_analytics/summary_spec.rb | 2 +- spec/models/cycle_analytics/test_spec.rb | 2 +- 17 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 changelogs/unreleased/jej-fix-missing-access-check-on-issues.yml diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 6b9f37983c4..89d84809e3a 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -36,7 +36,7 @@ class Projects::BranchesController < Projects::ApplicationController execute(branch_name, ref) if params[:issue_iid] - issue = @project.issues.find_by(iid: params[:issue_iid]) + issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid]) SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue end diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index fd263960b93..ac639ef015b 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -6,7 +6,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController before_action :authorize_read_cycle_analytics! def show - @cycle_analytics = ::CycleAnalytics.new(@project, from: start_date(cycle_analytics_params)) + @cycle_analytics = ::CycleAnalytics.new(@project, current_user, from: start_date(cycle_analytics_params)) stats_values, cycle_analytics_json = generate_cycle_analytics_data diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 5685d0f4e7c..52517381c65 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -16,13 +16,7 @@ class Projects::TodosController < Projects::ApplicationController @issuable ||= begin case params[:issuable_type] when "issue" - issue = @project.issues.find(params[:issuable_id]) - - if can?(current_user, :read_issue, issue) - issue - else - render_404 - end + IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) when "merge_request" @project.merge_requests.find(params[:issuable_id]) end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index a48f22cee07..36005035f68 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -41,6 +41,14 @@ class IssuableFinder sort(items) end + def find(*params) + execute.find(*params) + end + + def find_by(*params) + execute.find_by(*params) + end + def group return @group if defined?(@group) diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index cb8e088d21d..ba4ee6fcf9d 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -1,14 +1,15 @@ class CycleAnalytics STAGES = %i[issue plan code test review staging production].freeze - def initialize(project, from:) + def initialize(project, current_user, from:) @project = project + @current_user = current_user @from = from @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil) end def summary - @summary ||= Summary.new(@project, from: @from) + @summary ||= Summary.new(@project, @current_user, from: @from) end def permissions(user:) diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb index b46db449bf3..82f53d17ddd 100644 --- a/app/models/cycle_analytics/summary.rb +++ b/app/models/cycle_analytics/summary.rb @@ -1,12 +1,13 @@ class CycleAnalytics class Summary - def initialize(project, from:) + def initialize(project, current_user, from:) @project = project + @current_user = current_user @from = from end def new_issues - @project.issues.created_after(@from).count + IssuesFinder.new(@current_user, project_id: @project.id).execute.created_after(@from).count end def commits diff --git a/changelogs/unreleased/jej-fix-missing-access-check-on-issues.yml b/changelogs/unreleased/jej-fix-missing-access-check-on-issues.yml new file mode 100644 index 00000000000..844fba9a107 --- /dev/null +++ b/changelogs/unreleased/jej-fix-missing-access-check-on-issues.yml @@ -0,0 +1,4 @@ +--- +title: Fix missing access checks on issue lookup using IssuableFinder +merge_request: +author: diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index f7cf006efd6..b88586b8678 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -94,6 +94,24 @@ describe Projects::BranchesController do branch_name: branch, issue_iid: issue.iid end + + context 'without issue feature access' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + project.team.truncate + end + + it "doesn't post a system note" do + expect(SystemNoteService).not_to receive(:new_issue_branch) + + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + issue_iid: issue.iid + end + end end end diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb index 936320a3709..193a3f6b5a3 100644 --- a/spec/controllers/projects/todo_controller_spec.rb +++ b/spec/controllers/projects/todo_controller_spec.rb @@ -4,7 +4,7 @@ describe Projects::TodosController do include ApiHelpers let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project) } @@ -42,7 +42,7 @@ describe Projects::TodosController do end end - context 'when not authorized' do + context 'when not authorized for project' do it 'does not create todo for issue that user has no access to' do sign_in(user) expect do @@ -60,6 +60,19 @@ describe Projects::TodosController do expect(response).to have_http_status(302) end end + + context 'when not authorized for issue' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + sign_in(user) + end + + it "doesn't create todo" do + expect{ go }.not_to change { user.todos.count } + expect(response).to have_http_status(404) + end + end end end diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 7691d690db0..7771785ead3 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#code', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } context 'with deployment' do generate_cycle_analytics_spec( diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index f649b44d367..5ed3d37f2fb 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#issue', models: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :issue, diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index 2cdefbeef21..baf3e3241a1 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#plan', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :plan, diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 1f5e5cab92d..21b9c6e7150 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#production', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :production, diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 0ed080a42b1..158621d59a4 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#review', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :review, diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index af1c4477ddb..dad653964b7 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#staging', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :staging, diff --git a/spec/models/cycle_analytics/summary_spec.rb b/spec/models/cycle_analytics/summary_spec.rb index 9d67bc82cba..725bc68b25f 100644 --- a/spec/models/cycle_analytics/summary_spec.rb +++ b/spec/models/cycle_analytics/summary_spec.rb @@ -4,7 +4,7 @@ describe CycleAnalytics::Summary, models: true do let(:project) { create(:project) } let(:from) { Time.now } let(:user) { create(:user, :admin) } - subject { described_class.new(project, from: from) } + subject { described_class.new(project, user, from: from) } describe "#new_issues" do it "finds the number of issues created after the 'from date'" do diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 02ddfeed9c1..2313724e8f3 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -6,7 +6,7 @@ describe 'CycleAnalytics#test', feature: true do let(:project) { create(:project) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } - subject { CycleAnalytics.new(project, from: from_date) } + subject { CycleAnalytics.new(project, user, from: from_date) } generate_cycle_analytics_spec( phase: :test, -- cgit v1.2.1 From 3bf34face4cacf07ca973705c261369b1f596626 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 22 Nov 2016 10:25:04 +0000 Subject: Merge branch 'jej-use-issuable-finder-instead-of-access-check' into 'security' Replace issue access checks with use of IssuableFinder Split from !2024 to partially solve https://gitlab.com/gitlab-org/gitlab-ce/issues/23867 ## Which fixes are in this MR? :warning: - Potentially untested :bomb: - No test coverage :traffic_light: - Test coverage of some sort exists (a test failed when error raised) :vertical_traffic_light: - Test coverage of return value (a test failed when nil used) :white_check_mark: - Permissions check tested ### Issue lookup with access check Using `visible_to_user` likely makes these security issues too. See [Code smells](#code-smells). - [x] :vertical_traffic_light: app/finders/notes_finder.rb:15 [`visible_to_user`] - [x] :traffic_light: app/views/layouts/nav/_project.html.haml:73 [`visible_to_user`] [`.count`] - [x] :white_check_mark: app/services/merge_requests/build_service.rb:84 [`issue.try(:confidential?)`] - [x] :white_check_mark: lib/api/issues.rb:112 [`visible_to_user`] - CHANGELOG: Prevented API returning issues set to 'Only team members' to everyone - [x] :white_check_mark: lib/api/helpers.rb:126 [`can?(current_user, :read_issue, issue)`] Maybe here too? - [x] :white_check_mark: lib/gitlab/search_results.rb:53 [`visible_to_user`] ### Previous discussions - [ ] https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2024/diffs#b2ff264eddf9819d7693c14ae213d941494fe2b3_128_126 - [ ] https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2024/diffs#7b6375270d22f880bdcb085e47b519b426a5c6c7_87_87 See merge request !2031 --- app/finders/issuable_finder.rb | 2 +- app/finders/notes_finder.rb | 2 +- app/models/project.rb | 4 +- app/services/merge_requests/build_service.rb | 2 +- app/views/layouts/nav/_project.html.haml | 2 +- ...use-issuable-finder-instead-of-access-check.yml | 4 ++ lib/api/helpers.rb | 4 +- lib/api/issues.rb | 2 +- lib/gitlab/search_results.rb | 2 +- spec/lib/gitlab/project_search_results_spec.rb | 9 ++++ spec/lib/gitlab/search_results_spec.rb | 51 ++++++++++++++-------- spec/models/project_spec.rb | 16 +++++-- spec/requests/api/issues_spec.rb | 18 ++++++++ spec/services/merge_requests/build_service_spec.rb | 12 +++++ 14 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 changelogs/unreleased/jej-use-issuable-finder-instead-of-access-check.yml diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 36005035f68..9a74e36870b 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -21,7 +21,7 @@ class IssuableFinder attr_accessor :current_user, :params - def initialize(current_user, params) + def initialize(current_user, params = {}) @current_user = current_user @params = params end diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 0b7832e6583..a653a6d59c6 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -12,7 +12,7 @@ class NotesFinder when "commit" project.notes.for_commit_id(target_id).non_diff_notes when "issue" - project.issues.visible_to_user(current_user).find(target_id).notes.inc_author + IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author when "merge_request" project.merge_requests.find(target_id).mr_and_commit_notes.inc_author when "snippet", "project_snippet" diff --git a/app/models/project.rb b/app/models/project.rb index c61e63461e0..f01cb613b85 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -687,9 +687,9 @@ class Project < ActiveRecord::Base self.id end - def get_issue(issue_id) + def get_issue(issue_id, current_user) if default_issues_tracker? - issues.find_by(iid: issue_id) + IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) else ExternalIssue.new(issue_id, self) end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index dd0d738674e..bebfca7537b 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -81,7 +81,7 @@ module MergeRequests commit = commits.first merge_request.title = commit.title merge_request.description ||= commit.description.try(:strip) - elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?) + elsif iid && issue = merge_request.target_project.get_issue(iid, current_user) case issue when Issue merge_request.title = "Resolve \"#{issue.title}\"" diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 99a58bbb676..701bcd3ab71 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -70,7 +70,7 @@ %span Issues - if @project.default_issues_tracker? - %span.badge.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count) + %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do diff --git a/changelogs/unreleased/jej-use-issuable-finder-instead-of-access-check.yml b/changelogs/unreleased/jej-use-issuable-finder-instead-of-access-check.yml new file mode 100644 index 00000000000..c0b6f50052c --- /dev/null +++ b/changelogs/unreleased/jej-use-issuable-finder-instead-of-access-check.yml @@ -0,0 +1,4 @@ +--- +title: Replace issue access checks with use of IssuableFinder +merge_request: +author: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 79a83496eee..34d9c3c6932 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -128,9 +128,7 @@ module API end def find_project_issue(id) - issue = user_project.issues.find(id) - not_found! unless can?(current_user, :read_issue, issue) - issue + IssuesFinder.new(current_user, project_id: user_project.id).find(id) end def paginate(relation) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 029be7519f5..049b4fb214c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -122,7 +122,7 @@ module API # GET /projects/:id/issues?milestone=1.0.0&state=closed # GET /issues?iid=42 get ":id/issues" do - issues = user_project.issues.inc_notes_with_associations.visible_to_user(current_user) + issues = IssuesFinder.new(current_user, project_id: user_project.id).execute.inc_notes_with_associations issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil? diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 2690938fe82..47d8599e298 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -50,7 +50,7 @@ module Gitlab end def issues - issues = Issue.visible_to_user(current_user).where(project_id: project_ids_relation) + issues = IssuesFinder.new(current_user).execute.where(project_id: project_ids_relation) if query =~ /#(\d+)\z/ issues = issues.where(iid: $1) diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index a0fdad87eee..3cd9863ec6a 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -65,6 +65,14 @@ describe Gitlab::ProjectSearchResults, lib: true do end end + it 'does not list issues on private projects' do + issue = create(:issue, project: project) + + results = described_class.new(user, project, issue.title) + + expect(results.objects('issues')).not_to include issue + end + describe 'confidential issues' do let(:query) { 'issue' } let(:author) { create(:user) } @@ -72,6 +80,7 @@ describe Gitlab::ProjectSearchResults, lib: true do let(:non_member) { create(:user) } let(:member) { create(:user) } let(:admin) { create(:admin) } + let(:project) { create(:empty_project, :internal) } let!(:issue) { create(:issue, project: project, title: 'Issue 1') } let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) } let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) } diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index dfbefad6367..f23e3522625 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -12,35 +12,48 @@ describe Gitlab::SearchResults do let!(:milestone) { create(:milestone, project: project, title: 'foo') } let(:results) { described_class.new(user, Project.all, 'foo') } - describe '#projects_count' do - it 'returns the total amount of projects' do - expect(results.projects_count).to eq(1) + context 'as a user with access' do + before do + project.team << [user, :developer] end - end - describe '#issues_count' do - it 'returns the total amount of issues' do - expect(results.issues_count).to eq(1) + describe '#projects_count' do + it 'returns the total amount of projects' do + expect(results.projects_count).to eq(1) + end end - end - describe '#merge_requests_count' do - it 'returns the total amount of merge requests' do - expect(results.merge_requests_count).to eq(1) + describe '#issues_count' do + it 'returns the total amount of issues' do + expect(results.issues_count).to eq(1) + end + end + + describe '#merge_requests_count' do + it 'returns the total amount of merge requests' do + expect(results.merge_requests_count).to eq(1) + end end - end - describe '#milestones_count' do - it 'returns the total amount of milestones' do - expect(results.milestones_count).to eq(1) + describe '#milestones_count' do + it 'returns the total amount of milestones' do + expect(results.milestones_count).to eq(1) + end end end + it 'does not list issues on private projects' do + private_project = create(:empty_project, :private) + issue = create(:issue, project: private_project, title: 'foo') + + expect(results.objects('issues')).not_to include issue + end + describe 'confidential issues' do - let(:project_1) { create(:empty_project) } - let(:project_2) { create(:empty_project) } - let(:project_3) { create(:empty_project) } - let(:project_4) { create(:empty_project) } + let(:project_1) { create(:empty_project, :internal) } + let(:project_2) { create(:empty_project, :internal) } + let(:project_3) { create(:empty_project, :internal) } + let(:project_4) { create(:empty_project, :internal) } let(:query) { 'issue' } let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) } let(:author) { create(:user) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index da38254d1bc..8abcce42ce0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -361,10 +361,15 @@ describe Project, models: true do describe '#get_issue' do let(:project) { create(:empty_project) } let!(:issue) { create(:issue, project: project) } + let(:user) { create(:user) } + + before do + project.team << [user, :developer] + end context 'with default issues tracker' do it 'returns an issue' do - expect(project.get_issue(issue.iid)).to eq issue + expect(project.get_issue(issue.iid, user)).to eq issue end it 'returns count of open issues' do @@ -372,7 +377,12 @@ describe Project, models: true do end it 'returns nil when no issue found' do - expect(project.get_issue(999)).to be_nil + expect(project.get_issue(999, user)).to be_nil + end + + it "returns nil when user doesn't have access" do + user = create(:user) + expect(project.get_issue(issue.iid, user)).to eq nil end end @@ -382,7 +392,7 @@ describe Project, models: true do end it 'returns an ExternalIssue' do - issue = project.get_issue('FOO-1234') + issue = project.get_issue('FOO-1234', user) expect(issue).to be_kind_of(ExternalIssue) expect(issue.iid).to eq 'FOO-1234' expect(issue.project).to eq project diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index b17553211d2..ae7994af981 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -365,6 +365,24 @@ describe API::API, api: true do let(:base_url) { "/projects/#{project.id}" } let(:title) { milestone.title } + it "returns 404 on private projects for other users" do + private_project = create(:empty_project, :private) + create(:issue, project: private_project) + + get api("/projects/#{private_project.id}/issues", non_member) + + expect(response).to have_http_status(404) + end + + it 'returns no issues when user has access to project but not issues' do + restricted_project = create(:empty_project, :public, issues_access_level: ProjectFeature::PRIVATE) + create(:issue, project: restricted_project) + + get api("/projects/#{restricted_project.id}/issues", non_member) + + expect(json_response).to eq([]) + end + it 'returns project issues without confidential issues for non project members' do get api("#{base_url}/issues", non_member) expect(response).to have_http_status(200) diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 3f5df049ea2..dc945ca4868 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -24,6 +24,8 @@ describe MergeRequests::BuildService, services: true do end before do + project.team << [user, :guest] + allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare) allow(project).to receive(:commit).and_return(commit_1) allow(project).to receive(:commit).and_return(commit_2) @@ -168,6 +170,16 @@ describe MergeRequests::BuildService, services: true do expect(merge_request.title).to eq("Resolve \"#{issue.title}\"") end + context 'when issue is not accessible to user' do + before do + project.team.truncate + end + + it 'uses branch title as the merge request title' do + expect(merge_request.title).to eq("#{issue.iid} fix issue") + end + end + context 'issue does not exist' do let(:source_branch) { "#{issue.iid.succ}-fix-issue" } -- cgit v1.2.1 From 25e1bbd1a80f3139504ad86f6728cde8a564d5da Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 29 Nov 2016 08:37:02 +0100 Subject: fix blob controller spec failure --- app/controllers/projects/blob_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 9940263ae24..398122b3073 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -119,7 +119,7 @@ class Projects::BlobController < Projects::ApplicationController from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) if from_merge_request && @target_branch == @ref diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + - "##{hexdigest(@path)}" + "#file-path-#{hexdigest(@path)}" else namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) end -- cgit v1.2.1 From 280afe0a6480185f61c4f107724367bd5a170b2a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 29 Nov 2016 10:40:56 +0100 Subject: fix blob controller spec failure - updated not to use file-path- --- app/controllers/projects/blob_controller.rb | 2 +- spec/controllers/projects/blob_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 398122b3073..9940263ae24 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -119,7 +119,7 @@ class Projects::BlobController < Projects::ApplicationController from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) if from_merge_request && @target_branch == @ref diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + - "#file-path-#{hexdigest(@path)}" + "##{hexdigest(@path)}" else namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 1c2b0a4a45c..3efef757ae2 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -67,7 +67,7 @@ describe Projects::BlobController do put :update, mr_params after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) - file_anchor = "#file-path-#{Digest::SHA1.hexdigest('CHANGELOG')}" + file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}" expect(response).to redirect_to(after_edit_path + file_anchor) end -- cgit v1.2.1 From adb3f3d4e494e8f8d41c1b9e676e395a49cd96b2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 25 Nov 2016 12:51:12 +0100 Subject: Move MWPS document to new location --- doc/intro/README.md | 2 +- doc/user/project/merge_requests.md | 2 +- .../merge_requests/merge_when_build_succeeds.md | 47 ++-------------------- .../merge_requests/merge_when_pipeline_succeeds.md | 46 +++++++++++++++++++++ doc/workflow/README.md | 4 +- doc/workflow/merge_when_build_succeeds.md | 2 +- 6 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 doc/user/project/merge_requests/merge_when_pipeline_succeeds.md diff --git a/doc/intro/README.md b/doc/intro/README.md index 6deed35b7c9..1df6a52ce8a 100644 --- a/doc/intro/README.md +++ b/doc/intro/README.md @@ -23,7 +23,7 @@ Create merge requests and review code. - [Fork a project and contribute to it](../workflow/forking_workflow.md) - [Create a new merge request](../gitlab-basics/add-merge-request.md) - [Automatically close issues from merge requests](../user/project/issues/automatic_issue_closing.md) -- [Automatically merge when pipeline succeeds](../user/project/merge_requests/merge_when_build_succeeds.md) +- [Automatically merge when pipeline succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md) - [Revert any commit](../user/project/merge_requests/revert_changes.md) - [Cherry-pick any commit](../user/project/merge_requests/cherry_pick_changes.md) diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md index e76428d41f3..be09337319f 100644 --- a/doc/user/project/merge_requests.md +++ b/doc/user/project/merge_requests.md @@ -26,7 +26,7 @@ more CI builds running, you can set it to be merged automatically when CI pipeline succeeds. This way, you don't have to wait for the pipeline to finish and remember to merge the request manually. -[Learn more about merging when pipeline succeeds.](merge_requests/merge_when_build_succeeds.md) +[Learn more about merging when pipeline succeeds.](merge_requests/merge_when_pipeline_succeeds.md) ## Resolve discussion comments in merge requests reviews diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md index 75ad18b28cf..2167fdfbf7e 100644 --- a/doc/user/project/merge_requests/merge_when_build_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -1,46 +1,5 @@ -# Merge When Pipeline Succeeds +This document was moved to [merge_when_pipeline_succeeds](merge_when_pipeline_succeeds.md). -When reviewing a merge request that looks ready to merge but still has one or -more CI builds running, you can set it to be merged automatically when the -builds pipeline succeeds. This way, you don't have to wait for the builds to -finish and remember to merge the request manually. +>[Introduced][ce-7135] by the "Rename MWBS service to Merge When Pipeline Succeeds" change. -![Enable](img/merge_when_build_succeeds_enable.png) - -When you hit the "Merge When Pipeline Succeeds" button, the status of the merge -request will be updated to represent the impending merge. If you cannot wait -for the pipeline to succeed and want to merge immediately, this option is -available in the dropdown menu on the right of the main button. - -Both team developers and the author of the merge request have the option to -cancel the automatic merge if they find a reason why it shouldn't be merged -after all. - -![Status](img/merge_when_build_succeeds_status.png) - -When the pipeline succeeds, the merge request will automatically be merged. -When the pipeline fails, the author gets a chance to retry any failed builds, -or to push new commits to fix the failure. - -When the builds are retried and succeed on the second try, the merge request -will automatically be merged after all. When the merge request is updated with -new commits, the automatic merge is automatically canceled to allow the new -changes to be reviewed. - -## Only allow merge requests to be merged if the pipeline succeeds - -> **Note:** -You need to have builds configured to enable this feature. - -You can prevent merge requests from being merged if their pipeline did not succeed. - -Navigate to your project's settings page, select the -**Only allow merge requests to be merged if the pipeline succeeds** check box and -hit **Save** for the changes to take effect. - -![Only allow merge if pipeline succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) - -From now on, every time the pipeline fails you will not be able to merge the -merge request from the UI, until you make all relevant builds pass. - -![Only allow merge if pipeline succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png) +[ce-7135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7135 diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md new file mode 100644 index 00000000000..75ad18b28cf --- /dev/null +++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md @@ -0,0 +1,46 @@ +# Merge When Pipeline Succeeds + +When reviewing a merge request that looks ready to merge but still has one or +more CI builds running, you can set it to be merged automatically when the +builds pipeline succeeds. This way, you don't have to wait for the builds to +finish and remember to merge the request manually. + +![Enable](img/merge_when_build_succeeds_enable.png) + +When you hit the "Merge When Pipeline Succeeds" button, the status of the merge +request will be updated to represent the impending merge. If you cannot wait +for the pipeline to succeed and want to merge immediately, this option is +available in the dropdown menu on the right of the main button. + +Both team developers and the author of the merge request have the option to +cancel the automatic merge if they find a reason why it shouldn't be merged +after all. + +![Status](img/merge_when_build_succeeds_status.png) + +When the pipeline succeeds, the merge request will automatically be merged. +When the pipeline fails, the author gets a chance to retry any failed builds, +or to push new commits to fix the failure. + +When the builds are retried and succeed on the second try, the merge request +will automatically be merged after all. When the merge request is updated with +new commits, the automatic merge is automatically canceled to allow the new +changes to be reviewed. + +## Only allow merge requests to be merged if the pipeline succeeds + +> **Note:** +You need to have builds configured to enable this feature. + +You can prevent merge requests from being merged if their pipeline did not succeed. + +Navigate to your project's settings page, select the +**Only allow merge requests to be merged if the pipeline succeeds** check box and +hit **Save** for the changes to take effect. + +![Only allow merge if pipeline succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) + +From now on, every time the pipeline fails you will not be able to merge the +merge request from the UI, until you make all relevant builds pass. + +![Only allow merge if pipeline succeeds message](img/merge_when_build_succeeds_only_if_succeeds_msg.png) diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 57a0d21c7a7..59a806de210 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -25,12 +25,12 @@ - [Merge Requests](../user/project/merge_requests.md) - [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md) - [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md) - - [Merge when pipeline succeeds](../user/project/merge_requests/merge_when_build_succeeds.md) + - [Merge when pipeline succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md) - [Resolve discussion comments in merge requests reviews](../user/project/merge_requests/merge_request_discussion_resolution.md) - [Resolve merge conflicts in the UI](../user/project/merge_requests/resolve_conflicts.md) - [Revert changes in the UI](../user/project/merge_requests/revert_changes.md) - [Merge requests versions](../user/project/merge_requests/versions.md) - ["Work In Progress" merge requests](../user/project/merge_requests/work_in_progress_merge_requests.md) - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md) -- [Importing from SVN, GitHub, BitBucket, etc](importing/README.md) +- [Importing from SVN, GitHub, Bitbucket, etc](importing/README.md) - [Todos](todos.md) diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md index 95afd12ebdb..b4f6d6117de 100644 --- a/doc/workflow/merge_when_build_succeeds.md +++ b/doc/workflow/merge_when_build_succeeds.md @@ -1 +1 @@ -This document was moved to [user/project/merge_requests/merge_when_build_succeeds](../user/project/merge_requests/merge_when_build_succeeds.md). +This document was moved to [merge_when_pipeline_succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md). -- cgit v1.2.1 From a32dd93a4c5c0ae647beac425ed23e4b9c3d27f2 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 29 Nov 2016 12:41:11 +0100 Subject: fix started_at check --- app/serializers/analytics_build_entity.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index 206a7eadbcf..a0db5b8f0f4 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity end expose :duration, as: :total_time do |build| - build_started?(build) ? distance_of_time_as_hash(build.duration.to_f) : {} + build.duration ? distance_of_time_as_hash(build.duration.to_f) : {} end expose :branch do @@ -37,8 +37,4 @@ class AnalyticsBuildEntity < Grape::Entity def url_to(route, build, id = nil) public_send("#{route}_url", build.project.namespace, build.project, id || build) end - - def build_started?(build) - build.duration && build[:started_at] - end end -- cgit v1.2.1 From 8b3ab222c3370303e39aad9574c59b960fbfc299 Mon Sep 17 00:00:00 2001 From: Lee Matos Date: Tue, 29 Nov 2016 01:47:06 +0000 Subject: Fixing typo & Clarifying Key name --- doc/integration/bitbucket.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 556d71b8b76..9122dc62e39 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -123,7 +123,7 @@ To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa` which translates to `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages and to -`/home/git/.ssh/bitbucket_rsa.pub` for installations from source. +`/home/git/.ssh/bitbucket_rsa` for installations from source. --- @@ -199,7 +199,7 @@ Your GitLab server is now able to connect to Bitbucket over SSH. You should be able to see the "Import projects from Bitbucket" option on the New Project page enabled. -## Acknowledgemts +## Acknowledgements Special thanks to the writer behind the following article: -- cgit v1.2.1 From 41bf093662a24cc6b68eba3503b56ac44b7f6e69 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 29 Nov 2016 16:51:50 +0530 Subject: CE-specific changes gitlab-org/gitlab-ee#1137 - Extract all common {push,merge} access level model code into the `ProtectedBranchAccess` module - Use the HTTP verb to define controller specs --- app/models/concerns/protected_branch_access.rb | 9 +++++++++ app/models/protected_branch/merge_access_level.rb | 9 --------- app/models/protected_branch/push_access_level.rb | 6 +----- spec/controllers/autocomplete_controller_spec.rb | 4 ++-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb index 7fd0905ee81..9dd4d9c6f24 100644 --- a/app/models/concerns/protected_branch_access.rb +++ b/app/models/concerns/protected_branch_access.rb @@ -2,6 +2,9 @@ module ProtectedBranchAccess extend ActiveSupport::Concern included do + belongs_to :protected_branch + delegate :project, to: :protected_branch + scope :master, -> { where(access_level: Gitlab::Access::MASTER) } scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) } end @@ -9,4 +12,10 @@ module ProtectedBranchAccess def humanize self.class.human_access_levels[self.access_level] end + + def check_access(user) + return true if user.is_admin? + + project.team.max_member_access(user.id) >= access_level + end end diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 806b3ccd275..771e3376613 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER] } @@ -13,10 +10,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base Gitlab::Access::DEVELOPER => "Developers + Masters" }.with_indifferent_access end - - def check_access(user) - return true if user.is_admin? - - project.team.max_member_access(user.id) >= access_level - end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 92e9c51d883..14610cb42b7 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS] } @@ -18,8 +15,7 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base def check_access(user) return false if access_level == Gitlab::Access::NO_ACCESS - return true if user.is_admin? - project.team.max_member_access(user.id) >= access_level + super end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index d9a86346c81..0d1545040f1 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -4,7 +4,7 @@ describe AutocompleteController do let!(:project) { create(:project) } let!(:user) { create(:user) } - context 'users and members' do + context 'GET users' do let!(:user2) { create(:user) } let!(:non_member) { create(:user) } @@ -180,7 +180,7 @@ describe AutocompleteController do end end - context 'projects' do + context 'GET projects' do let(:authorized_project) { create(:project) } let(:authorized_search_project) { create(:project, name: 'rugged') } -- cgit v1.2.1 From a48ef15620bd479bcbe1c47e60eaa4c84dbd0f8f Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 28 Nov 2016 16:44:24 +0200 Subject: Remove unnecessary database indexes --- .../unreleased/removing_unnecessary_indexes.yml | 4 +++ .../20161128142110_remove_unnecessary_indexes.rb | 33 ++++++++++++++++++++++ db/schema.rb | 9 ------ 3 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/removing_unnecessary_indexes.yml create mode 100644 db/migrate/20161128142110_remove_unnecessary_indexes.rb diff --git a/changelogs/unreleased/removing_unnecessary_indexes.yml b/changelogs/unreleased/removing_unnecessary_indexes.yml new file mode 100644 index 00000000000..01314ab5585 --- /dev/null +++ b/changelogs/unreleased/removing_unnecessary_indexes.yml @@ -0,0 +1,4 @@ +--- +title: Remove unnecessary database indices +merge_request: +author: diff --git a/db/migrate/20161128142110_remove_unnecessary_indexes.rb b/db/migrate/20161128142110_remove_unnecessary_indexes.rb new file mode 100644 index 00000000000..9deab19782e --- /dev/null +++ b/db/migrate/20161128142110_remove_unnecessary_indexes.rb @@ -0,0 +1,33 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveUnnecessaryIndexes < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + remove_index :labels, column: :group_id if index_exists?(:labels, :group_id) + remove_index :award_emoji, column: :user_id if index_exists?(:award_emoji, :user_id) + remove_index :ci_builds, column: :commit_id if index_exists?(:ci_builds, :commit_id) + remove_index :deployments, column: :project_id if index_exists?(:deployments, :project_id) + remove_index :deployments, column: ["project_id", "environment_id"] if index_exists?(:deployments, ["project_id", "environment_id"]) + remove_index :lists, column: :board_id if index_exists?(:lists, :board_id) + remove_index :milestones, column: :project_id if index_exists?(:milestones, :project_id) + remove_index :notes, column: :project_id if index_exists?(:notes, :project_id) + remove_index :users_star_projects, column: :user_id if index_exists?(:users_star_projects, :user_id) + end + + def down + add_concurrent_index :labels, :group_id + add_concurrent_index :award_emoji, :user_id + add_concurrent_index :ci_builds, :commit_id + add_concurrent_index :deployments, :project_id + add_concurrent_index :deployments, ["project_id", "environment_id"] + add_concurrent_index :lists, :board_id + add_concurrent_index :milestones, :project_id + add_concurrent_index :notes, :project_id + add_concurrent_index :users_star_projects, :user_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 3d630a148f0..0d510c8a269 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -132,7 +132,6 @@ ActiveRecord::Schema.define(version: 20161128161412) do add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree add_index "award_emoji", ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree - add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree create_table "boards", force: :cascade do |t| t.integer "project_id", null: false @@ -220,7 +219,6 @@ ActiveRecord::Schema.define(version: 20161128161412) do add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree - add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree add_index "ci_builds", ["gl_project_id"], name: "index_ci_builds_on_gl_project_id", using: :btree add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree @@ -410,9 +408,7 @@ ActiveRecord::Schema.define(version: 20161128161412) do end add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree - add_index "deployments", ["project_id", "environment_id"], name: "index_deployments_on_project_id_and_environment_id", using: :btree add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree - add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree create_table "emails", force: :cascade do |t| t.integer "user_id", null: false @@ -570,7 +566,6 @@ ActiveRecord::Schema.define(version: 20161128161412) do end add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree - add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree create_table "lfs_objects", force: :cascade do |t| t.string "oid", null: false @@ -601,7 +596,6 @@ ActiveRecord::Schema.define(version: 20161128161412) do end add_index "lists", ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true, using: :btree - add_index "lists", ["board_id"], name: "index_lists_on_board_id", using: :btree add_index "lists", ["label_id"], name: "index_lists_on_label_id", using: :btree create_table "members", force: :cascade do |t| @@ -727,7 +721,6 @@ ActiveRecord::Schema.define(version: 20161128161412) do add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree - add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} @@ -790,7 +783,6 @@ ActiveRecord::Schema.define(version: 20161128161412) do add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree - add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "notification_settings", force: :cascade do |t| @@ -1243,7 +1235,6 @@ ActiveRecord::Schema.define(version: 20161128161412) do add_index "users_star_projects", ["project_id"], name: "index_users_star_projects_on_project_id", using: :btree add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree - add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree create_table "web_hooks", force: :cascade do |t| t.string "url", limit: 2000 -- cgit v1.2.1 From a49e9949c6bc474c8bfd4016d9c6c3b59776772f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 28 Nov 2016 11:13:32 +0100 Subject: Rename `MergeRequest#pipeline` to `head_pipeline` --- .../projects/merge_requests_controller.rb | 15 ++++++++------- app/models/ci/pipeline.rb | 2 +- app/models/merge_request.rb | 6 +++--- app/services/merge_requests/base_service.rb | 4 ++-- .../projects/issues/_merge_requests.html.haml | 6 +++--- .../merge_requests/_merge_request.html.haml | 4 ++-- .../projects/merge_requests/widget/_show.html.haml | 2 +- lib/api/merge_requests.rb | 2 +- spec/models/merge_request_spec.rb | 18 +++++++++--------- spec/requests/api/merge_requests_spec.rb | 2 +- .../add_todo_when_build_fails_service_spec.rb | 12 +++++++++--- .../merge_when_build_succeeds_service_spec.rb | 22 ++++++++++++++++------ 12 files changed, 56 insertions(+), 39 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a2225cc8343..f47df8b623b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -325,16 +325,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? - unless @merge_request.pipeline + unless @merge_request.head_pipeline @status = :failed return end - if @merge_request.pipeline.active? + if @merge_request.head_pipeline.active? MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) .execute(@merge_request) @status = :merge_when_build_succeeds - elsif @merge_request.pipeline.success? + elsif @merge_request.head_pipeline.success? # This can be triggered when a user clicks the auto merge button while # the tests finish at about the same time MergeWorker.perform_async(@merge_request.id, current_user.id, params) @@ -398,7 +398,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_status - pipeline = @merge_request.pipeline + pipeline = @merge_request.head_pipeline + if pipeline status = pipeline.status coverage = pipeline.try(:coverage) @@ -534,7 +535,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_widget_vars - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline end def define_commit_vars @@ -563,7 +564,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_pipelines_vars @pipelines = @merge_request.all_pipelines - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 end @@ -631,7 +632,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_when_build_succeeds_active? params[:merge_when_build_succeeds].present? && - @merge_request.pipeline && @merge_request.pipeline.active? + @merge_request.head_pipeline && @merge_request.head_pipeline.active? end def build_merge_request diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4294a10e9e3..fabbf97d4db 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -317,7 +317,7 @@ module Ci def merge_requests @merge_requests ||= project.merge_requests .where(source_branch: self.ref) - .select { |merge_request| merge_request.pipeline.try(:id) == self.id } + .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } end private diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 38d8c15e6b0..64990f8134e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -678,7 +678,7 @@ class MergeRequest < ActiveRecord::Base def mergeable_ci_state? return true unless project.only_allow_merge_if_build_succeeds? - !pipeline || pipeline.success? || pipeline.skipped? + !head_pipeline || head_pipeline.success? || head_pipeline.skipped? end def environments @@ -774,10 +774,10 @@ class MergeRequest < ActiveRecord::Base commits.map(&:sha) end - def pipeline + def head_pipeline return unless diff_head_sha && source_project - @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) + @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end def all_pipelines diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 58f69a41e14..800fd39c424 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -55,7 +55,7 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref).each do |merge_request| - next unless pipeline == merge_request.pipeline + next unless pipeline == merge_request.head_pipeline yield merge_request end @@ -63,7 +63,7 @@ module MergeRequests def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref).each do |merge_request| - pipeline = merge_request.pipeline + pipeline = merge_request.head_pipeline next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index 747bfa554cb..d48923b422a 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -2,12 +2,12 @@ %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') %ul.unstyled-list.related-merge-requests - - has_any_ci = @merge_requests.any?(&:pipeline) + - has_any_ci = @merge_requests.any?(&:head_pipeline) - @merge_requests.each do |merge_request| %li %span.merge-request-ci-status - - if merge_request.pipeline - = render_pipeline_status(merge_request.pipeline) + - if merge_request.head_pipeline + = render_pipeline_status(merge_request.head_pipeline) - elsif has_any_ci = icon('blank fw') %span.merge-request-id diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 9ffcc48eb80..fa189ae62d8 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -15,9 +15,9 @@ = icon('ban') CLOSED - - if merge_request.pipeline + - if merge_request.head_pipeline %li - = render_pipeline_status(merge_request.pipeline) + = render_pipeline_status(merge_request.head_pipeline) - if merge_request.open? && merge_request.broken? %li diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 608fdf1c5f5..a8918c85dde 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -14,7 +14,7 @@ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", - ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", + ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_message: { normal: "Build {{status}} for \"{{title}}\"", preparing: "{{status}} build for \"{{title}}\"" diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 90fa588b455..97baebc1d27 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -192,7 +192,7 @@ module API should_remove_source_branch: params[:should_remove_source_branch] } - if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? + if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). execute(merge_request) else diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 58ccd056328..26034cb1c7b 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -570,7 +570,7 @@ describe MergeRequest, models: true do end end - describe '#pipeline' do + describe '#head_pipeline' do describe 'when the source project exists' do it 'returns the latest pipeline' do pipeline = double(:ci_pipeline, ref: 'master') @@ -581,7 +581,7 @@ describe MergeRequest, models: true do with('master', '123abc'). and_return(pipeline) - expect(subject.pipeline).to eq(pipeline) + expect(subject.head_pipeline).to eq(pipeline) end end @@ -589,7 +589,7 @@ describe MergeRequest, models: true do it 'returns nil' do allow(subject).to receive(:source_project).and_return(nil) - expect(subject.pipeline).to be_nil + expect(subject.head_pipeline).to be_nil end end end @@ -857,7 +857,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.update(status: 'failed') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_falsey } @@ -866,7 +866,7 @@ describe MergeRequest, models: true do context 'and a successful pipeline is associated' do before do pipeline.update(status: 'success') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -875,7 +875,7 @@ describe MergeRequest, models: true do context 'and a skipped pipeline is associated' do before do pipeline.update(status: 'skipped') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -883,7 +883,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -896,7 +896,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.statuses << create(:commit_status, status: 'failed', project: project) - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -904,7 +904,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 3ecf3eea5f5..edc985b765b 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -466,7 +466,7 @@ describe API::API, api: true do end it "enables merge when build succeeds if the ci is active" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index a44312dd363..bb7830c7eea 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -20,13 +20,19 @@ describe MergeRequests::AddTodoWhenBuildFailsService do let(:todo_service) { TodoService.new } let(:merge_request) do - create(:merge_request, merge_user: user, source_branch: 'master', - target_branch: 'feature', source_project: project, target_project: project, + create(:merge_request, merge_user: user, + source_branch: 'master', + target_branch: 'feature', + source_project: project, + target_project: project, state: 'opened') end before do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest) + .to receive(:head_pipeline) + .and_return(pipeline) + allow(service).to receive(:todo_service).and_return(todo_service) end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index c0164138713..963d9573ac4 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -21,7 +21,10 @@ describe MergeRequests::MergeWhenBuildSucceedsService do context 'first time enabling' do before do - allow(merge_request).to receive(:pipeline).and_return(pipeline) + allow(merge_request) + .to receive(:head_pipeline) + .and_return(pipeline) + service.execute(merge_request) end @@ -43,8 +46,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } before do - allow(mr_merge_if_green_enabled).to receive(:pipeline).and_return(pipeline) - allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true) + allow(mr_merge_if_green_enabled).to receive(:head_pipeline) + .and_return(pipeline) + + allow(mr_merge_if_green_enabled).to receive(:mergeable?) + .and_return(true) + allow(pipeline).to receive(:success?).and_return(true) end @@ -138,9 +145,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do before do # This behavior of MergeRequest: we instantiate a new object - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do - Ci::Pipeline.find(pipeline.id) - end + # + allow_any_instance_of(MergeRequest) + .to receive(:head_pipeline) + .and_wrap_original do + Ci::Pipeline.find(pipeline.id) + end end it "doesn't merge if any of stages failed" do -- cgit v1.2.1 From 651eccda62218532b24ff75384c943256bf70224 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 29 Nov 2016 11:57:16 +0100 Subject: Expose timestamp in build entity used by serializer --- app/serializers/build_entity.rb | 3 +++ spec/serializers/build_entity_spec.rb | 25 ++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb index cf1c418a88e..b5384e6462b 100644 --- a/app/serializers/build_entity.rb +++ b/app/serializers/build_entity.rb @@ -16,6 +16,9 @@ class BuildEntity < Grape::Entity path_to(:play_namespace_project_build, build) end + expose :created_at + expose :updated_at + private def path_to(route, build) diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb index 6dcfaec259e..60c9642ee2c 100644 --- a/spec/serializers/build_entity_spec.rb +++ b/spec/serializers/build_entity_spec.rb @@ -1,23 +1,30 @@ require 'spec_helper' describe BuildEntity do + let(:build) { create(:ci_build) } + let(:entity) do described_class.new(build, request: double) end subject { entity.as_json } - context 'when build is a regular job' do - let(:build) { create(:ci_build) } + it 'contains paths to build page and retry action' do + expect(subject).to include(:build_path, :retry_path) + end - it 'contains paths to build page and retry action' do - expect(subject).to include(:build_path, :retry_path) - expect(subject).not_to include(:play_path) - end + it 'does not contain sensitive information' do + expect(subject).not_to include(/token/) + expect(subject).not_to include(/variables/) + end + + it 'contains timestamps' do + expect(subject).to include(:created_at, :updated_at) + end - it 'does not contain sensitive information' do - expect(subject).not_to include(/token/) - expect(subject).not_to include(/variables/) + context 'when build is a regular job' do + it 'does not contain path to play action' do + expect(subject).not_to include(:play_path) end end -- cgit v1.2.1 From 867dcdf75012a7b7c8b131d46b275b28fa5bfa00 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 28 Nov 2016 18:18:48 -0200 Subject: Alert user when logged in user email is not the same as the invitation --- app/views/invites/show.html.haml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml index 2fd4859c1c6..882fdf1317d 100644 --- a/app/views/invites/show.html.haml +++ b/app/views/invites/show.html.haml @@ -6,7 +6,7 @@ - if inviter = @member.created_by by = link_to inviter.name, user_url(inviter) - to join + to join - case @member.source - when Project - project = @member.source @@ -20,11 +20,18 @@ = link_to group.name, group_url(group) as #{@member.human_access}. -- if @member.source.users.include?(current_user) +- is_member = @member.source.users.include?(current_user) + +- if is_member %p However, you are already a member of this #{@member.source.is_a?(Group) ? "group" : "project"}. Sign in using a different account to accept the invitation. -- else + +- if @member.invite_email != current_user.email + %p + Note that this invitation was sent to #{mail_to @member.invite_email}, but you are signed in as #{link_to current_user.to_reference, user_url(current_user)} with email #{mail_to current_user.email}. + +- unless is_member .actions = link_to "Accept invitation", accept_invite_url(@token), method: :post, class: "btn btn-success" = link_to "Decline", decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10" -- cgit v1.2.1 From 6a7bc1c12219aedbbc3343f8e00aadbaab9ec266 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 28 Nov 2016 18:21:45 -0200 Subject: Add a CHANGELOG entry --- changelogs/unreleased/improve-invite-accept-page.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/improve-invite-accept-page.yml diff --git a/changelogs/unreleased/improve-invite-accept-page.yml b/changelogs/unreleased/improve-invite-accept-page.yml new file mode 100644 index 00000000000..8a09a5ae42f --- /dev/null +++ b/changelogs/unreleased/improve-invite-accept-page.yml @@ -0,0 +1,4 @@ +--- +title: Add note to the invite page when the logged in user email is not the same as the invitation +merge_request: +author: -- cgit v1.2.1 From 49491e63b818b757bd05c9b1d0b447f1baf30db0 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 28 Nov 2016 23:38:14 -0800 Subject: Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1 The "Enqueue Now" button would not work in the admin panel due to changes in the Web extension interface. Closes #24376 --- Gemfile | 2 +- Gemfile.lock | 9 +++++---- changelogs/unreleased/sh-update-sidekiq-cron.yml | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/sh-update-sidekiq-cron.yml diff --git a/Gemfile b/Gemfile index 1d686199557..350ecd0ca02 100644 --- a/Gemfile +++ b/Gemfile @@ -133,7 +133,7 @@ gem 'acts-as-taggable-on', '~> 4.0' # Background jobs gem 'sidekiq', '~> 4.2' -gem 'sidekiq-cron', '~> 0.4.0' +gem 'sidekiq-cron', '~> 0.4.4' gem 'redis-namespace', '~> 1.5.2' gem 'sidekiq-limit_fetch', '~> 3.4' diff --git a/Gemfile.lock b/Gemfile.lock index bf9702b2562..40aa91423c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -614,7 +614,8 @@ GEM rubyntlm (0.5.2) rubypants (0.2.0) rubyzip (1.2.0) - rufus-scheduler (3.1.10) + rufus-scheduler (3.3.0) + tzinfo rugged (0.24.0) safe_yaml (1.0.4) sanitize (2.1.0) @@ -650,10 +651,10 @@ GEM connection_pool (~> 2.2, >= 2.2.0) rack-protection (~> 1.5) redis (~> 3.2, >= 3.2.1) - sidekiq-cron (0.4.0) + sidekiq-cron (0.4.4) redis-namespace (>= 1.5.2) rufus-scheduler (>= 2.0.24) - sidekiq (>= 4.0.0) + sidekiq (>= 4.2.1) sidekiq-limit_fetch (3.4.0) sidekiq (>= 4) simplecov (0.12.0) @@ -925,7 +926,7 @@ DEPENDENCIES sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) sidekiq (~> 4.2) - sidekiq-cron (~> 0.4.0) + sidekiq-cron (~> 0.4.4) sidekiq-limit_fetch (~> 3.4) simplecov (= 0.12.0) slack-notifier (~> 1.2.0) diff --git a/changelogs/unreleased/sh-update-sidekiq-cron.yml b/changelogs/unreleased/sh-update-sidekiq-cron.yml new file mode 100644 index 00000000000..d79ba817a18 --- /dev/null +++ b/changelogs/unreleased/sh-update-sidekiq-cron.yml @@ -0,0 +1,4 @@ +--- +title: Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1 +merge_request: +author: -- cgit v1.2.1 From 5e05f9c5c2cfaf14cc0337c23ff54f86c5d5f5a8 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 4 Nov 2016 11:43:02 +0000 Subject: Add StackProf to the Gemfile, along with a utility to get a profile for a spec --- Gemfile | 2 + Gemfile.lock | 2 + bin/rspec-stackprof | 16 ++++++ doc/development/performance.md | 110 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100755 bin/rspec-stackprof diff --git a/Gemfile b/Gemfile index 1d686199557..1d86daf580b 100644 --- a/Gemfile +++ b/Gemfile @@ -309,6 +309,8 @@ group :development, :test do gem 'knapsack', '~> 1.11.0' gem 'activerecord_sane_schema_dumper', '0.2' + + gem 'stackprof', '~> 0.2.10' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index bf9702b2562..50e69425980 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -691,6 +691,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + stackprof (0.2.10) state_machines (0.4.0) state_machines-activemodel (0.4.0) activemodel (>= 4.1, < 5.1) @@ -937,6 +938,7 @@ DEPENDENCIES spring-commands-teaspoon (~> 0.0.2) sprockets (~> 3.7.0) sprockets-es6 (~> 0.9.2) + stackprof (~> 0.2.10) state_machines-activerecord (~> 0.4.0) sys-filesystem (~> 1.1.6) teaspoon (~> 1.1.0) diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof new file mode 100755 index 00000000000..df79feb201d --- /dev/null +++ b/bin/rspec-stackprof @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +require 'stackprof' +$:.unshift 'spec' +require 'rails_helper' + +filename = ARGV[0].split('/').last +interval = ENV.fetch('INTERVAL', 1000).to_i +limit = ENV.fetch('LIMIT', 20) +output_file = "tmp/#{filename}.dump" + +StackProf.run(mode: :wall, out: output_file, interval: interval) do + RSpec::Core::Runner.run(ARGV, $stderr, $stdout) +end + +system("stackprof #{output_file} --text --limit #{limit}") diff --git a/doc/development/performance.md b/doc/development/performance.md index 8337c2d9cb3..5c43ae7b79a 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -101,6 +101,116 @@ In short: 5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's `Benchmark` module. +## Profiling + +By collecting snapshots of process state at regular intervals, profiling allows +you to see where time is spent in a process. The [StackProf](https://github.com/tmm1/stackprof) +gem is included in GitLab's development environment, allowing you to investigate +the behaviour of suspect code in detail. + +It's important to note that profiling an application *alters its performance*, +and will generally be done *in an unrepresentative environment*. In particular, +a method is not necessarily troublesome just because it is executed many times, +or takes a long time to execute. Profiles are tools you can use to better +understand what is happening in an application - using that information wisely +is up to you! + +Keeping that in mind, to create a profile, identify (or create) a spec that +exercises the troublesome code path, then run it using the `bin/rspec-stackprof` +helper, e.g.: + +``` +$ LIMIT=10 bin/rspec-stackprof spec/policies/project_policy_spec.rb +8/8 |====== 100 ======>| Time: 00:00:18 + +Finished in 18.19 seconds (files took 4.8 seconds to load) +8 examples, 0 failures + +================================== + Mode: wall(1000) + Samples: 17033 (5.59% miss rate) + GC: 1901 (11.16%) +================================== + TOTAL (pct) SAMPLES (pct) FRAME + 6000 (35.2%) 2566 (15.1%) Sprockets::Cache::FileStore#get + 2018 (11.8%) 888 (5.2%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache + 1338 (7.9%) 640 (3.8%) ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#execute + 3125 (18.3%) 394 (2.3%) Sprockets::Cache::FileStore#safe_open + 913 (5.4%) 301 (1.8%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache + 288 (1.7%) 288 (1.7%) ActiveRecord::Attribute#initialize + 246 (1.4%) 246 (1.4%) Sprockets::Cache::FileStore#safe_stat + 295 (1.7%) 193 (1.1%) block (2 levels) in class_attribute + 187 (1.1%) 187 (1.1%) block (4 levels) in class_attribute +``` + +You can limit the specs that are run by passing any arguments `rspec` would +normally take. + +The output is sorted by the `Samples` column by default. This is the number of +samples taken where the method is the one currently being executed. The `Total` +column shows the number of samples taken where the method, or any of the methods +it calls, were being executed. + +To create a graphical view of the call stack: + +```shell +$ stackprof tmp/project_policy_spec.rb.dump --graphviz > project_policy_spec.dot +$ dot -Tsvg project_policy_spec.dot > project_policy_spec.svg +``` + +To load the profile in [kcachegrind](https://kcachegrind.github.io/): + +``` +$ stackprof tmp/project_policy_spec.dump --callgrind > project_policy_spec.callgrind +$ kcachegrind project_policy_spec.callgrind # Linux +$ qcachegrind project_policy_spec.callgrind # Mac +``` + +It may be useful to zoom in on a specific method, e.g.: + +``` +$ stackprof tmp/project_policy_spec.rb.dump --method warm_asset_cache +TestEnv#warm_asset_cache (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/spec/support/test_env.rb:164) + samples: 0 self (0.0%) / 6288 total (36.9%) + callers: + 6288 ( 100.0%) block (2 levels) in + callees (6288 total): + 6288 ( 100.0%) Capybara::RackTest::Driver#visit + code: + | 164 | def warm_asset_cache + | 165 | return if warm_asset_cache? + | 166 | return unless defined?(Capybara) + | 167 | + 6288 (36.9%) | 168 | Capybara.current_session.driver.visit '/' + | 169 | end +$ stackprof tmp/project_policy_spec.rb.dump --method BasePolicy#abilities +BasePolicy#abilities (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/app/policies/base_policy.rb:79) + samples: 0 self (0.0%) / 50 total (0.3%) + callers: + 25 ( 50.0%) BasePolicy.abilities + 25 ( 50.0%) BasePolicy#collect_rules + callees (50 total): + 25 ( 50.0%) ProjectPolicy#rules + 25 ( 50.0%) BasePolicy#collect_rules + code: + | 79 | def abilities + | 80 | return RuleSet.empty if @user && @user.blocked? + | 81 | return anonymous_abilities if @user.nil? + 50 (0.3%) | 82 | collect_rules { rules } + | 83 | end +``` + +Since the profile includes the work done by the test suite as well as the +application code, these profiles can be used to investigate slow tests as well. +However, for smaller runs (like this example), this means that the cost of +setting up the test suite will tend to dominate. + +It's also possible to modify the application code in-place to output profiles +whenever a particular code path is triggered without going through the test +suite first. See the +[StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md) +for details. + ## Importance of Changes When working on performance improvements, it's important to always ask yourself -- cgit v1.2.1 From ddf288f0d0d6bed07303b8e34288c41114f3a5ab Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sat, 26 Nov 2016 13:55:47 +0000 Subject: Moved groups above projects --- .../projects/_zero_authorized_projects.html.haml | 23 +++++++++++----------- ...ld-be-below-new-group-on-the-welcome-screen.yml | 4 ++++ 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/24135-new-project-should-be-below-new-group-on-the-welcome-screen.yml diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index fdea834ff45..4a55aac0df6 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -4,6 +4,18 @@ Welcome to GitLab %p.blank-state-text Code, test, and deploy together + +- if current_user.can_create_group? + .blank-state + .blank-state-icon + = custom_icon("group", size: 50) + %h3.blank-state-title + You can create a group for several dependent projects. + %p.blank-state-text + Groups are the best way to manage projects and members. + = link_to new_group_path, class: "btn btn-new" do + New group + .blank-state .blank-state-icon = custom_icon("project", size: 50) @@ -21,17 +33,6 @@ = link_to new_project_path, class: "btn btn-new" do New project -- if current_user.can_create_group? - .blank-state - .blank-state-icon - = custom_icon("group", size: 50) - %h3.blank-state-title - You can create a group for several dependent projects. - %p.blank-state-text - Groups are the best way to manage projects and members. - = link_to new_group_path, class: "btn btn-new" do - New group - -if publicish_project_count > 0 .blank-state .blank-state-icon diff --git a/changelogs/unreleased/24135-new-project-should-be-below-new-group-on-the-welcome-screen.yml b/changelogs/unreleased/24135-new-project-should-be-below-new-group-on-the-welcome-screen.yml new file mode 100644 index 00000000000..855e4e1ba1d --- /dev/null +++ b/changelogs/unreleased/24135-new-project-should-be-below-new-group-on-the-welcome-screen.yml @@ -0,0 +1,4 @@ +--- +title: Moved new projects button below new group button on the welcome screen +merge_request: 7770 +author: -- cgit v1.2.1 From 30c3eed89b023f84d9f1fdd8ddb6af4c7e7a05eb Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Sun, 27 Nov 2016 23:49:59 +0100 Subject: Adds hoverstates for collapsed Issue/Merge Request sidebar --- app/assets/stylesheets/pages/issuable.scss | 16 +++++++++++----- ...rstates-for-collapsed-issue-merge-request-sidebar.yml | 4 ++++ 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 773155fe80a..7aad99eee4e 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -132,7 +132,7 @@ display: none; } - .btn-clipboard { + .btn-clipboard:hover { color: $gl-gray; } } @@ -235,6 +235,10 @@ padding-bottom: 10px; color: #999; + &:hover { + color: $gl-gray; + } + span { display: block; margin-top: 0; @@ -244,15 +248,17 @@ display: none; } + .avatar:hover { + border-color: #999; + } + .btn-clipboard { border: none; + color: #999; &:hover { background: transparent; - } - - i { - color: #999; + color: $gl-gray; } } } diff --git a/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml b/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml new file mode 100644 index 00000000000..2c3ba1dfe44 --- /dev/null +++ b/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml @@ -0,0 +1,4 @@ +--- +title: Adds hoverstates for collapsed Issue/Merge Request sidebar +merge_request: !7777 +author: -- cgit v1.2.1 From 46859cb984523affdcbc5ba9697883b290c1bf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 29 Nov 2016 15:56:50 +0100 Subject: Fix a transient spec failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sorting by created_at can lead to uncertainties if two records are created at the same time. Signed-off-by: Rémy Coutable --- spec/requests/projects/cycle_analytics_events_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 705dbb7d1c0..5c90fd9bad9 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -40,7 +40,7 @@ describe 'cycle analytics events' do expect(json_response['events']).not_to be_empty - first_mr_iid = MergeRequest.order(created_at: :desc).pluck(:iid).first.to_s + first_mr_iid = project.merge_requests.order(id: :desc).pluck(:iid).first.to_s expect(json_response['events'].first['iid']).to eq(first_mr_iid) end -- cgit v1.2.1 From c145413d1acb58addb199f0e5bfd909e0a695a5c Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Mon, 14 Nov 2016 22:37:13 +0000 Subject: Remove JSX/React eslint plugins. Change airbnb eslint config package to `eslint-config-airbnb-base` and update plugins. Change `airbnb` to `airbnb-base` for .eslintrc `extends` value. Added changelog entry Made sure all plugins and envs are set Corrected new failing specs --- .eslintignore | 1 + .eslintrc | 7 +++---- app/assets/javascripts/labels_select.js | 2 +- app/assets/javascripts/notes.js | 2 +- .../unreleased/remove-jsx-react-eslint-plugins.yml | 5 +++++ package.json | 10 ++++------ spec/javascripts/build_spec.js.es6 | 2 +- .../environments/environment_actions_spec.js.es6 | 4 ++-- .../environments/environment_item_spec.js.es6 | 20 ++++++++++---------- .../environments/environments_store_spec.js.es6 | 8 ++++---- spec/javascripts/smart_interval_spec.js.es6 | 2 +- .../vue_common_components/commit_spec.js.es6 | 12 ++++++------ 12 files changed, 39 insertions(+), 36 deletions(-) create mode 100644 changelogs/unreleased/remove-jsx-react-eslint-plugins.yml diff --git a/.eslintignore b/.eslintignore index d9c2233c9d7..93de4b10dfe 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ +/coverage/ /coverage-javascript/ /public/ /tmp/ diff --git a/.eslintrc b/.eslintrc index 788a88487d8..b80dcec9d1d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,15 +1,14 @@ { "env": { + "jquery": true, "browser": true, "es6": true }, - "extends": "airbnb", + "extends": "airbnb-base", "globals": { - "$": false, "_": false, "gl": false, - "gon": false, - "jQuery": false + "gon": false }, "plugins": [ "filenames" diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 812d5cde685..f334f35594d 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */ +/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */ (function() { this.LabelsSelect = (function() { function LabelsSelect() { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 47e7b6f831b..0ca0e255595 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */ +/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */ /*= require autosave */ /*= require autosize */ diff --git a/changelogs/unreleased/remove-jsx-react-eslint-plugins.yml b/changelogs/unreleased/remove-jsx-react-eslint-plugins.yml new file mode 100644 index 00000000000..6e02998b3a8 --- /dev/null +++ b/changelogs/unreleased/remove-jsx-react-eslint-plugins.yml @@ -0,0 +1,5 @@ +--- +title: Changed eslint airbnb config to the base airbnb config and corrected eslintrc + plugins and envs +merge_request: 7470 +author: Luke "Jared" Bennett diff --git a/package.json b/package.json index 350e4cd80c9..961989f8012 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,11 @@ "eslint-report": "npm run eslint -- --format html --output-file ./eslint-report.html" }, "devDependencies": { - "eslint": "^3.1.1", - "eslint-config-airbnb": "^12.0.0", + "eslint": "^3.10.1", + "eslint-config-airbnb-base": "^10.0.1", "eslint-plugin-filenames": "^1.1.0", - "eslint-plugin-import": "^1.16.0", - "eslint-plugin-jasmine": "^1.8.1", - "eslint-plugin-jsx-a11y": "^2.2.3", - "eslint-plugin-react": "^6.4.1", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-jasmine": "^2.1.0", "istanbul": "^0.4.5" } } diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 index d694727880f..3983cad4c13 100644 --- a/spec/javascripts/build_spec.js.es6 +++ b/spec/javascripts/build_spec.js.es6 @@ -109,7 +109,7 @@ describe('Build', () => { expect($.ajax.calls.count()).toBe(2); let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1); expect(url).toBe( - `${BUILD_URL}/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}` + `${BUILD_URL}/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}`, ); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6 index c9ac7a73fd0..76e81233e89 100644 --- a/spec/javascripts/environments/environment_actions_spec.js.es6 +++ b/spec/javascripts/environments/environment_actions_spec.js.es6 @@ -28,10 +28,10 @@ describe('Actions Component', () => { }); expect( - component.$el.querySelectorAll('.dropdown-menu li').length + component.$el.querySelectorAll('.dropdown-menu li').length, ).toEqual(actionsMock.length); expect( - component.$el.querySelector('.dropdown-menu li a').getAttribute('href') + component.$el.querySelector('.dropdown-menu li a').getAttribute('href'), ).toEqual(actionsMock[0].play_path); }); }); diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index 3c15e3b7719..14e90a9dd1b 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -141,18 +141,18 @@ describe('Environment item', () => { describe('With deployment', () => { it('should render deployment internal id', () => { expect( - component.$el.querySelector('.deployment-column span').textContent + component.$el.querySelector('.deployment-column span').textContent, ).toContain(environment.last_deployment.iid); expect( - component.$el.querySelector('.deployment-column span').textContent + component.$el.querySelector('.deployment-column span').textContent, ).toContain('#'); }); describe('With user information', () => { it('should render user avatar with link to profile', () => { expect( - component.$el.querySelector('.js-deploy-user-container').getAttribute('href') + component.$el.querySelector('.js-deploy-user-container').getAttribute('href'), ).toEqual(environment.last_deployment.user.web_url); }); }); @@ -160,13 +160,13 @@ describe('Environment item', () => { describe('With build url', () => { it('Should link to build url provided', () => { expect( - component.$el.querySelector('.build-link').getAttribute('href') + component.$el.querySelector('.build-link').getAttribute('href'), ).toEqual(environment.last_deployment.deployable.build_path); }); it('Should render deployable name and id', () => { expect( - component.$el.querySelector('.build-link').getAttribute('href') + component.$el.querySelector('.build-link').getAttribute('href'), ).toEqual(environment.last_deployment.deployable.build_path); }); }); @@ -174,7 +174,7 @@ describe('Environment item', () => { describe('With commit information', () => { it('should render commit component', () => { expect( - component.$el.querySelector('.js-commit-component') + component.$el.querySelector('.js-commit-component'), ).toBeDefined(); }); }); @@ -183,7 +183,7 @@ describe('Environment item', () => { describe('With manual actions', () => { it('Should render actions component', () => { expect( - component.$el.querySelector('.js-manual-actions-container') + component.$el.querySelector('.js-manual-actions-container'), ).toBeDefined(); }); }); @@ -191,7 +191,7 @@ describe('Environment item', () => { describe('With external URL', () => { it('should render external url component', () => { expect( - component.$el.querySelector('.js-external-url-container') + component.$el.querySelector('.js-external-url-container'), ).toBeDefined(); }); }); @@ -199,7 +199,7 @@ describe('Environment item', () => { describe('With stop action', () => { it('Should render stop action component', () => { expect( - component.$el.querySelector('.js-stop-component-container') + component.$el.querySelector('.js-stop-component-container'), ).toBeDefined(); }); }); @@ -207,7 +207,7 @@ describe('Environment item', () => { describe('With retry action', () => { it('Should render rollback component', () => { expect( - component.$el.querySelector('.js-rollback-component-container') + component.$el.querySelector('.js-rollback-component-container'), ).toBeDefined(); }); }); diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6 index 9b0b3cb1c65..17c00acf63e 100644 --- a/spec/javascripts/environments/environments_store_spec.js.es6 +++ b/spec/javascripts/environments/environments_store_spec.js.es6 @@ -5,11 +5,11 @@ //= require ./mock_data (() => { - beforeEach(() => { - gl.environmentsList.EnvironmentsStore.create(); - }); - describe('Store', () => { + beforeEach(() => { + gl.environmentsList.EnvironmentsStore.create(); + }); + it('should start with a blank state', () => { expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(0); expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(0); diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js.es6 index 651d1f0f975..ed6166a25a8 100644 --- a/spec/javascripts/smart_interval_spec.js.es6 +++ b/spec/javascripts/smart_interval_spec.js.es6 @@ -37,7 +37,7 @@ const intervalConfig = this.smartInterval.cfg; const iterationCount = 4; const maxIntervalAfterIterations = intervalConfig.startingInterval * - Math.pow(intervalConfig.incrementByFactorOf, (iterationCount - 1)); // 40 + (intervalConfig.incrementByFactorOf ** (iterationCount - 1)); // 40 const currentInterval = interval.getCurrentInterval(); // Provide some flexibility for performance of testing environment diff --git a/spec/javascripts/vue_common_components/commit_spec.js.es6 b/spec/javascripts/vue_common_components/commit_spec.js.es6 index 0e3b82967c1..b1dbc8bd5fa 100644 --- a/spec/javascripts/vue_common_components/commit_spec.js.es6 +++ b/spec/javascripts/vue_common_components/commit_spec.js.es6 @@ -74,26 +74,26 @@ describe('Commit component', () => { describe('Given commit title and author props', () => { it('Should render a link to the author profile', () => { expect( - component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href') + component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'), ).toEqual(props.author.web_url); }); it('Should render the author avatar with title and alt attributes', () => { expect( - component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title') + component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title'), ).toContain(props.author.username); expect( - component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt') + component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'), ).toContain(`${props.author.username}'s avatar`); }); }); it('should render the commit title', () => { expect( - component.$el.querySelector('a.commit-row-message').getAttribute('href') + component.$el.querySelector('a.commit-row-message').getAttribute('href'), ).toEqual(props.commit_url); expect( - component.$el.querySelector('a.commit-row-message').textContent + component.$el.querySelector('a.commit-row-message').textContent, ).toContain(props.title); }); }); @@ -119,7 +119,7 @@ describe('Commit component', () => { }); expect( - component.$el.querySelector('.commit-title span').textContent + component.$el.querySelector('.commit-title span').textContent, ).toContain('Cant find HEAD commit for this branch'); }); }); -- cgit v1.2.1 From f08385142d427dc73c2a7e668652e2a5d9c7ea88 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Tue, 29 Nov 2016 10:00:02 -0800 Subject: Add blue back to sub nav active --- app/assets/stylesheets/framework/nav.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index a2787ede53c..1839ffa0976 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -94,6 +94,7 @@ &.active a { border-bottom: none; + color: $link-underline-blue; } a { @@ -103,7 +104,6 @@ &:hover, &:active, &:focus { - color: $black; border-bottom: none; } } -- cgit v1.2.1 From 530d222815b62180145d3cbe89cd3602835de8ad Mon Sep 17 00:00:00 2001 From: Ryan Harris Date: Tue, 29 Nov 2016 13:39:57 -0500 Subject: Edit /spec/features/profiles/preferences_spec.rb to match changes in 084d90ac --- spec/features/profiles/preferences_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index d14a1158b67..a6b841c0210 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -73,7 +73,7 @@ describe 'Profile > Preferences', feature: true do expect(page.current_path).to eq starred_dashboard_projects_path end - click_link 'Your Projects' + click_link 'Your projects' expect(page).not_to have_content("You don't have starred projects yet") expect(page.current_path).to eq dashboard_projects_path -- cgit v1.2.1 From 500c0d5e419ecc39e823bdbfd49e4c5842621fa2 Mon Sep 17 00:00:00 2001 From: Luis Alonso Chavez Armendariz Date: Tue, 29 Nov 2016 11:44:07 -0700 Subject: Fix appearance in error pages --- app/views/errors/access_denied.html.haml | 16 +++++--- app/views/errors/encoding.html.haml | 14 ++++--- app/views/errors/git_not_found.html.haml | 17 ++++---- app/views/errors/not_found.html.haml | 14 ++++--- app/views/errors/omniauth_error.html.haml | 16 +++++--- app/views/layouts/errors.html.haml | 65 +++++++++++++++++++++++++++---- changelogs/unreleased/issue_24363.yml | 4 ++ public/404.html | 5 ++- public/422.html | 5 ++- public/500.html | 5 ++- public/502.html | 5 ++- public/503.html | 5 ++- 12 files changed, 129 insertions(+), 42 deletions(-) create mode 100644 changelogs/unreleased/issue_24363.yml diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index c034bbe430e..8bddbef3562 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,6 +1,10 @@ -- page_title "Access Denied" -%h1 403 -%h3 Access Denied -%hr -%p You are not allowed to access this page. -%p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} +- content_for(:title, 'Access Denied') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 403 +.container + %h3 Access Denied + %hr + %p You are not allowed to access this page. + %p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index 90cfbebfcc6..064ff14ad2c 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,5 +1,9 @@ -- page_title "Encoding Error" -%h1 500 -%h3 Encoding Error -%hr -%p Page can't be loaded because of an encoding error. +- content_for(:title, 'Encoding Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 500 +.container + %h3 Encoding Error + %hr + %p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index ff5d4cc1506..c5c12a410ac 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,7 +1,10 @@ -- page_title "Git Resource Not Found" -%h1 404 -%h3 Git Resource Not found -%hr -%p - Application can't get access to some branch or commit in your repository. It - may have been moved. +- content_for(:title, 'Git Resource Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 Git Resource Not found + %hr + %p Application can't get access to some branch or commit in your repository. It + may have been moved diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 3756b98ebb2..50a54a93cb5 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,5 +1,9 @@ -- page_title "Not Found" -%h1 404 -%h3 The resource you were looking for doesn't exist. -%hr -%p You may have mistyped the address or the page may have moved. +- content_for(:title, 'Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 The resource you were looking for doesn't exist. + %hr + %p You may have mistyped the address or the page may have moved. diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml index 3e70e98a24c..d91f1878cb6 100644 --- a/app/views/errors/omniauth_error.html.haml +++ b/app/views/errors/omniauth_error.html.haml @@ -1,9 +1,13 @@ -- page_title "Auth Error" -%h1 422 -%h3 Sign-in using #{@provider} auth failed -%hr -%p Sign-in failed because #{@error}. -%p There are couple of steps you can take: +- content_for(:title, 'Auth Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 422 +.container + %h3 Sign-in using #{@provider} auth failed + %hr + %p Sign-in failed because #{@error}. + %p There are couple of steps you can take: %ul %li Try logging in using your email diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index 7fbe065df00..a3b925f6afd 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,10 +1,59 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" - %body{class: "#{user_application_theme} application navless"} - = Gon::Base.render_data - = render "layouts/header/empty" - .container.navless-container - = render "layouts/flash" - .error-page - = yield + %head + %meta{:content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport"} + %title= yield(:title) + :css + body { + color: #666; + text-align: center; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: auto; + font-size: 14px; + } + + h1 { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; + } + + h2 { + font-size: 24px; + color: #666; + line-height: 1.5em; + } + + h3 { + color: #456; + font-size: 20px; + font-weight: normal; + line-height: 28px; + } + + hr { + max-width: 800px; + margin: 18px auto; + border: 0; + border-top: 1px solid #EEE; + border-bottom: 1px solid white; + } + + img { + max-width: 40vw; + display: block; + margin: 40px auto; + } + + .container { + margin: auto 20px; + } + + ul { + margin: auto; + text-align: left; + display:inline-block; + } +%body + = yield diff --git a/changelogs/unreleased/issue_24363.yml b/changelogs/unreleased/issue_24363.yml new file mode 100644 index 00000000000..0298890b477 --- /dev/null +++ b/changelogs/unreleased/issue_24363.yml @@ -0,0 +1,4 @@ +--- +title: Fix appearance in error pages +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/public/404.html b/public/404.html index 92b7f4da0b9..11b29d09a82 100644 --- a/public/404.html +++ b/public/404.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ + GitLab Logo

- GitLab Logo
404

diff --git a/public/422.html b/public/422.html index f625f8a33b7..9bd7cb4b7c8 100644 --- a/public/422.html +++ b/public/422.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ + GitLab Logo

- GitLab Logo
422

diff --git a/public/500.html b/public/500.html index d76c66ba92a..f92e8839f8d 100644 --- a/public/500.html +++ b/public/500.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ + GitLab Logo

- GitLab Logo
500

diff --git a/public/502.html b/public/502.html index 1a3c7efc769..c2be4f130a9 100644 --- a/public/502.html +++ b/public/502.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ + GitLab Logo

- GitLab Logo
502

diff --git a/public/503.html b/public/503.html index c1c4e3ffdb8..8850ffce362 100644 --- a/public/503.html +++ b/public/503.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ + GitLab Logo

- GitLab Logo
503

-- cgit v1.2.1 From 086da9214d2bde1f652f197b0e130f60eef12c2e Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 28 Nov 2016 20:15:18 -0600 Subject: fetch local parameters in _generic_commit_status.html.haml similar to how _build.html.haml handles them --- .../_generic_commit_status.html.haml | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 0b99e9f8756..ceaab8c7d45 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -1,3 +1,11 @@ +- ref = local_assigns.fetch(:ref, nil) +- commit_sha = local_assigns.fetch(:commit_sha, nil) +- retried = local_assigns.fetch(:retried, false) +- pipeline_link = local_assigns.fetch(:pipeline_link, false) +- stage = local_assigns.fetch(:stage, false) +- coverage = local_assigns.fetch(:coverage, false) +- runner = local_assigns.fetch(:runner, false) + %tr.generic_commit_status %td.status - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url @@ -12,10 +20,10 @@ - else %strong ##{generic_commit_status.id} - - if defined?(retried) && retried + - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.') - - if defined?(pipeline_link) && pipeline_link + - if pipeline_link %td = link_to pipeline_path(generic_commit_status.pipeline) do %span.pipeline-id ##{generic_commit_status.pipeline.id} @@ -25,25 +33,25 @@ - else %span.monospace API - - if defined?(commit_sha) && commit_sha + - if commit_sha %td = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - if defined?(ref) && ref + - if ref %td - if generic_commit_status.ref = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - else .light none - - if defined?(runner) && runner + - if runner %td - if generic_commit_status.try(:runner) = runner_link(generic_commit_status.runner) - else .light none - - if defined?(stage) && stage + - if stage %td = generic_commit_status.stage @@ -55,7 +63,7 @@ - generic_commit_status.tags.each do |tag| %span.label.label-primary = tag - - if defined?(retried) && retried + - if retried %span.label.label-warning retried %td.duration @@ -68,7 +76,7 @@ = icon("calendar") %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} - - if defined?(coverage) && coverage + - if coverage %td.coverage - if generic_commit_status.try(:coverage) #{generic_commit_status.coverage}% -- cgit v1.2.1 From 3e0651c7cd0c8dc402dc45387e08ae04731ab46c Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 28 Nov 2016 20:59:48 -0600 Subject: reorder generic commit status columns to match build status partial --- .../_generic_commit_status.html.haml | 51 ++++++++++++---------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index ceaab8c7d45..d9b34ba3c43 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -1,12 +1,12 @@ +- admin = local_assigns.fetch(:admin, false) - ref = local_assigns.fetch(:ref, nil) - commit_sha = local_assigns.fetch(:commit_sha, nil) - retried = local_assigns.fetch(:retried, false) - pipeline_link = local_assigns.fetch(:pipeline_link, false) - stage = local_assigns.fetch(:stage, false) - coverage = local_assigns.fetch(:coverage, false) -- runner = local_assigns.fetch(:runner, false) -%tr.generic_commit_status +%tr.generic_commit_status{class: ('retried' if retried)} %td.status - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url) @@ -16,13 +16,34 @@ %td.generic_commit_status-link - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url = link_to generic_commit_status.target_url do - %strong ##{generic_commit_status.id} + %span.build-link ##{generic_commit_status.id} - else - %strong ##{generic_commit_status.id} + %span.build-link ##{generic_commit_status.id} + + - if ref + - if generic_commit_status.ref + .icon-container + = generic_commit_status.tags.any? ? icon('tag') : icon('code-fork') + = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) + - else + .light none + .icon-container.commit-icon + = custom_icon("icon_commit") + + - if commit_sha + = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-id monospace" - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.') + .label-container + - if generic_commit_status.tags.any? + - generic_commit_status.tags.each do |tag| + %span.label.label-primary + = tag + - if retried + %span.label.label-warning retried + - if pipeline_link %td = link_to pipeline_path(generic_commit_status.pipeline) do @@ -33,18 +54,12 @@ - else %span.monospace API - - if commit_sha - %td - = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - - if ref + - if admin %td - - if generic_commit_status.ref - = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - - else - .light none + - if generic_commit_status.project + = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project) - - if runner + - if admin %td - if generic_commit_status.try(:runner) = runner_link(generic_commit_status.runner) @@ -58,14 +73,6 @@ %td = generic_commit_status.name - %td - - if generic_commit_status.tags.any? - - generic_commit_status.tags.each do |tag| - %span.label.label-primary - = tag - - if retried - %span.label.label-warning retried - %td.duration - if generic_commit_status.duration = icon("clock-o") -- cgit v1.2.1 From a2ea78a09d689d2f53c22859f8304e6c1288bf17 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 28 Nov 2016 21:04:50 -0600 Subject: reformat build duration and finish time to match /ci/builds/_build.html.haml --- .../_generic_commit_status.html.haml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index d9b34ba3c43..4fff082202d 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -73,17 +73,21 @@ %td = generic_commit_status.name - %td.duration + %td - if generic_commit_status.duration - = icon("clock-o") - = time_interval_in_words(generic_commit_status.duration) + %p.duration + = custom_icon("icon_timer") + = duration_in_numbers(generic_commit_status.duration) - %td.timestamp - if generic_commit_status.finished_at - = icon("calendar") - %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} + %p.finished-at + = icon("calendar") + %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} - - if coverage - %td.coverage + %td.coverage + - if coverage - if generic_commit_status.try(:coverage) #{generic_commit_status.coverage}% + + %td + -# empty column to match number of columns in ci/builds/_build.html.haml -- cgit v1.2.1 From 093dd5cef8d5130101218ce258aceb92a9d60a6a Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 28 Nov 2016 21:08:02 -0600 Subject: remove redundant if statement --- app/views/projects/ci/builds/_build.html.haml | 2 -- .../projects/generic_commit_statuses/_generic_commit_status.html.haml | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 8d9c15d0dc6..33b4e9329a2 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -66,8 +66,6 @@ %td - if build.project = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) - - - if admin %td - if build.try(:runner) = runner_link(build.runner) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 4fff082202d..131883b8106 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -58,8 +58,6 @@ %td - if generic_commit_status.project = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project) - - - if admin %td - if generic_commit_status.try(:runner) = runner_link(generic_commit_status.runner) -- cgit v1.2.1 From b7deda9405e3c7355bd470b4288b636e95bcb5b2 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 28 Nov 2016 21:08:29 -0600 Subject: collapse nested if statement --- app/views/projects/ci/builds/_build.html.haml | 5 ++--- .../generic_commit_statuses/_generic_commit_status.html.haml | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 33b4e9329a2..e75547c815f 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -91,9 +91,8 @@ %span #{time_ago_with_tooltip(build.finished_at)} %td.coverage - - if coverage - - if build.try(:coverage) - #{build.coverage}% + - if coverage && build.try(:coverage) + #{build.coverage}% %td .pull-right diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 131883b8106..7f751d9ae2e 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -83,9 +83,8 @@ %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} %td.coverage - - if coverage - - if generic_commit_status.try(:coverage) - #{generic_commit_status.coverage}% + - if coverage && generic_commit_status.try(:coverage) + #{generic_commit_status.coverage}% %td -# empty column to match number of columns in ci/builds/_build.html.haml -- cgit v1.2.1 From 9c6eaf8009ea4cb5a4b33610aad32a2da30f3d09 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 28 Nov 2016 21:29:12 -0600 Subject: add CHANGELOG.md entry for !7811 --- changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml diff --git a/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml b/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml new file mode 100644 index 00000000000..07cb53d5278 --- /dev/null +++ b/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml @@ -0,0 +1,4 @@ +--- +title: Update generic/external build status to match normal build status template +merge_request: 7811 +author: -- cgit v1.2.1 From 9ed87e5c365e42256111b11d65140e03668b2cc0 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Mon, 28 Nov 2016 11:53:41 -0800 Subject: 25044 Make md header tabs match nav tabs --- app/assets/stylesheets/framework/markdown_area.scss | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 42087c91530..4bd7ff8fefd 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -56,16 +56,9 @@ .md-header { .nav-links { - .active { - a { - border-bottom-color: #000; - } - } - a { padding-top: 0; line-height: 19px; - border-bottom: 1px solid $border-color; &.btn.btn-xs { padding: 2px 5px; -- cgit v1.2.1 From 09826be231aba3471766378007fc1e748c05c154 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Tue, 29 Nov 2016 21:05:30 +0000 Subject: Rewrite an HTTP link to use HTTPS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9bb5af6da2..61204630fd2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GitLab [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) -[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) +[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) -- cgit v1.2.1 From f5e8337c7bb7e218303a713440e31f44a66471d7 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 29 Nov 2016 14:49:43 +0500 Subject: Do not raise error in AutocompleteController#users when not authorized https://gitlab.com/gitlab-org/gitlab-ce/issues/25031 --- app/controllers/autocomplete_controller.rb | 2 +- .../unreleased/25031-do-not-raise-error-in-autocomplete.yml | 4 ++++ spec/controllers/autocomplete_controller_spec.rb | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 5c44637fdee..5f13353baa1 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -11,7 +11,7 @@ class AutocompleteController < ApplicationController @users = @users.reorder(:name) @users = @users.page(params[:page]) - if params[:todo_filter].present? + if params[:todo_filter].present? && current_user @users = @users.todo_authors(current_user.id, params[:todo_state_filter]) end diff --git a/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml new file mode 100644 index 00000000000..862de7c5db1 --- /dev/null +++ b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml @@ -0,0 +1,4 @@ +--- +title: Do not raise error in AutocompleteController#users when not authorized +merge_request: 7817 +author: Semyon Pupkov diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 0d1545040f1..ea2fd90a9b0 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -144,6 +144,15 @@ describe AutocompleteController do it { expect(body).to be_kind_of(Array) } it { expect(body.size).to eq 0 } end + + describe 'GET #users with todo filter' do + it 'gives an array of users' do + get :users, todo_filter: true + + expect(response.status).to eq 200 + expect(body).to be_kind_of(Array) + end + end end context 'author of issuable included' do -- cgit v1.2.1 From f7351b040b0b0737372d18201ddc955942e0e016 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 29 Nov 2016 15:08:11 +0800 Subject: Speed up Group security access specs This is the Group equivalent of 13ad9a745a392e0bf0cedd0e1f318c1acee9b969 --- .../security/group/internal_access_spec.rb | 123 +++++++++------------ .../features/security/group/private_access_spec.rb | 123 +++++++++------------ spec/features/security/group/public_access_spec.rb | 123 +++++++++------------ spec/support/matchers/access_matchers.rb | 31 +++--- 4 files changed, 181 insertions(+), 219 deletions(-) diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index 35fcef7a712..87cce32d6c6 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Internal Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :internal) } + let(:group) { create(:group, :internal) } let(:project) { create(:project, :internal, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be internal" do @@ -34,75 +21,75 @@ describe 'Internal Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 75a93342628..1d6b3e77c22 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Private Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :private) } + let(:group) { create(:group, :private) } let(:project) { create(:project, :private, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be private" do @@ -34,75 +21,75 @@ describe 'Private Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 6c5ee93970b..d7d76177269 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Public Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :public) } + let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be public" do @@ -34,75 +21,75 @@ describe 'Public Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb index 691d7e05f57..ceddb656596 100644 --- a/spec/support/matchers/access_matchers.rb +++ b/spec/support/matchers/access_matchers.rb @@ -7,7 +7,7 @@ module AccessMatchers extend RSpec::Matchers::DSL include Warden::Test::Helpers - def emulate_user(user, project = nil) + def emulate_user(user, membership = nil) case user when :user login_as(create(:user)) @@ -19,16 +19,17 @@ module AccessMatchers login_as(create(:user, external: true)) when User login_as(user) - when :owner - raise ArgumentError, "cannot emulate owner without project" unless project - - login_as(project.owner) - when *Gitlab::Access.sym_options.keys - raise ArgumentError, "cannot emulate user #{user} without project" unless project + when *Gitlab::Access.sym_options_with_owner.keys + raise ArgumentError, "cannot emulate #{user} without membership parent" unless membership role = user - user = create(:user) - project.public_send(:"add_#{role}", user) + + if role == :owner && membership.owner + user = membership.owner + else + user = create(:user) + membership.public_send(:"add_#{role}", user) + end login_as(user) else @@ -47,14 +48,14 @@ module AccessMatchers matcher :be_allowed_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code != 404 && current_path != new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'allowed') } @@ -62,14 +63,14 @@ module AccessMatchers matcher :be_denied_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code == 404 || current_path == new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'denied') } -- cgit v1.2.1 From 11de793c27d0d92d5a9feb90a830fcff90dd7f0c Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Thu, 24 Nov 2016 03:35:38 +0600 Subject: new system note design for commit discussion --- app/assets/stylesheets/pages/notes.scss | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e66c1f8d072..538ad22daa4 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -172,6 +172,126 @@ ul.notes { &.timeline-entry { padding: 14px 10px; } + + .system-note { + font-size: 14px; + padding: 0; + clear: both; + + &.timeline-entry::after { + clear: none; + } + + .system-note-message { + display: inline-block; + + &::first-letter { + text-transform: lowercase; + } + + a { + color: $gl-link-color; + text-decoration: none; + } + + p { + display: inline-block; + margin: 0; + + &::first-letter { + text-transform: lowercase; + } + } + } + + .timeline-content { + padding: 14px 12px; + } + + .note-body { + overflow: hidden; + + .system-note-commit-list-toggler { + display: none; + padding: 10px 0 0; + cursor: pointer; + position: relative; + z-index: 2; + + &:hover { + color: $gl-link-color; + text-decoration: underline; + } + } + + .note-text { + & p:first-child { + display: none; + } + + &.system-note-commit-list { + max-height: 63px; + overflow: hidden; + display: block; + + ul { + margin: 3px 0 3px 15px !important; + + li { + font-family: $monospace_font; + font-size: 12px; + } + } + + p:first-child { + display: none; + } + + p:last-child { + a { + color: $gl-text-color; + + &:hover { + color: $gl-link-color; + } + } + } + + &::after { + content: ''; + width: 100%; + height: 67px; + position: absolute; + left: 0; + bottom: 0; + background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%); + } + + &.hide-shade { + max-height: 100%; + overflow: auto; + + &::after { + display: none; + background: transparent; + } + } + } + } + } + + .timeline-icon { + display: none; + + .avatar { + visibility: hidden; + + .discussion-body & { + visibility: visible; + } + } + } + } } &.is-editting { -- cgit v1.2.1 From 5c6a748ec98fb1f5a8c1365a0273698385ac98fb Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Thu, 24 Nov 2016 04:18:00 +0600 Subject: changelog entry added --- .../unreleased/24894-style-system-note-in-commit-discussion.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml diff --git a/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml b/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml new file mode 100644 index 00000000000..7ddf0b46d4c --- /dev/null +++ b/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml @@ -0,0 +1,4 @@ +--- +title: Fixes system note style in commit discussion +merge_request: 7721 +author: -- cgit v1.2.1 From 28638356a20e64d5fb1ac6016fbe0f3645b16ecd Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 29 Nov 2016 14:07:25 +0600 Subject: removes redundant styles --- app/assets/stylesheets/pages/notes.scss | 116 -------------------------------- 1 file changed, 116 deletions(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 538ad22daa4..dd079859630 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -174,123 +174,7 @@ ul.notes { } .system-note { - font-size: 14px; padding: 0; - clear: both; - - &.timeline-entry::after { - clear: none; - } - - .system-note-message { - display: inline-block; - - &::first-letter { - text-transform: lowercase; - } - - a { - color: $gl-link-color; - text-decoration: none; - } - - p { - display: inline-block; - margin: 0; - - &::first-letter { - text-transform: lowercase; - } - } - } - - .timeline-content { - padding: 14px 12px; - } - - .note-body { - overflow: hidden; - - .system-note-commit-list-toggler { - display: none; - padding: 10px 0 0; - cursor: pointer; - position: relative; - z-index: 2; - - &:hover { - color: $gl-link-color; - text-decoration: underline; - } - } - - .note-text { - & p:first-child { - display: none; - } - - &.system-note-commit-list { - max-height: 63px; - overflow: hidden; - display: block; - - ul { - margin: 3px 0 3px 15px !important; - - li { - font-family: $monospace_font; - font-size: 12px; - } - } - - p:first-child { - display: none; - } - - p:last-child { - a { - color: $gl-text-color; - - &:hover { - color: $gl-link-color; - } - } - } - - &::after { - content: ''; - width: 100%; - height: 67px; - position: absolute; - left: 0; - bottom: 0; - background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%); - } - - &.hide-shade { - max-height: 100%; - overflow: auto; - - &::after { - display: none; - background: transparent; - } - } - } - } - } - - .timeline-icon { - display: none; - - .avatar { - visibility: hidden; - - .discussion-body & { - visibility: visible; - } - } - } } } -- cgit v1.2.1 From 89bd04cda1660fb328ece9c7cc3d94d4e87fec6a Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 29 Nov 2016 17:54:16 +0600 Subject: shows commits SHAs in monospcase and commit messaages in normal font --- app/assets/stylesheets/pages/notes.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e66c1f8d072..3ef4c51d6f1 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -97,7 +97,7 @@ ul.notes { ul { margin: 3px 0 3px 15px !important; - li { + .gfm-commit { font-family: $monospace_font; font-size: 12px; } -- cgit v1.2.1 From df085e41433529193636c5b40e59bd5e7cd3e20c Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 29 Nov 2016 17:56:12 +0600 Subject: makes ul bullets fully visible --- app/assets/stylesheets/pages/notes.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 3ef4c51d6f1..30922d96ae9 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -95,7 +95,7 @@ ul.notes { display: block; ul { - margin: 3px 0 3px 15px !important; + margin: 3px 0 3px 16 px !important; .gfm-commit { font-family: $monospace_font; -- cgit v1.2.1 From e77c867dba7f559f7156bfc8800d7d78c9dd31f9 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 29 Nov 2016 18:05:21 +0600 Subject: adjust padding in list --- app/assets/stylesheets/pages/notes.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 30922d96ae9..38f34a7ca9d 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -95,7 +95,7 @@ ul.notes { display: block; ul { - margin: 3px 0 3px 16 px !important; + margin: 3px 0 3px 16px !important; .gfm-commit { font-family: $monospace_font; -- cgit v1.2.1 From 49ae66fe95fb898129635aace008dcd510e677d9 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Tue, 29 Nov 2016 18:07:20 +0600 Subject: adjust commit list ul height --- app/assets/stylesheets/pages/notes.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 38f34a7ca9d..beef5c0a24f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -90,7 +90,7 @@ ul.notes { } &.system-note-commit-list { - max-height: 63px; + max-height: 70px; overflow: hidden; display: block; -- cgit v1.2.1 From fbbf177e3b604bebce3b10f8eea8920ff5606fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 28 Sep 2016 12:45:46 +0200 Subject: New `gitlab:workhorse:install` rake task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- ...l-rake-task-similar-to-gitlab-shell-install.yml | 4 + doc/install/installation.md | 28 +++-- doc/update/8.12-to-8.13.md | 2 +- lib/tasks/gitlab/shell.rake | 42 ++------ lib/tasks/gitlab/task_helpers.rake | 35 +++++- lib/tasks/gitlab/workhorse.rake | 23 ++++ spec/support/stub_configuration.rb | 4 + spec/tasks/gitlab/task_helpers_spec.rb | 87 +++++++++++++++ spec/tasks/gitlab/workhorse_rake_spec.rb | 117 +++++++++++++++++++++ 9 files changed, 295 insertions(+), 47 deletions(-) create mode 100644 changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml create mode 100644 lib/tasks/gitlab/workhorse.rake create mode 100644 spec/tasks/gitlab/task_helpers_spec.rb create mode 100644 spec/tasks/gitlab/workhorse_rake_spec.rb diff --git a/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml b/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml new file mode 100644 index 00000000000..54bd313f075 --- /dev/null +++ b/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml @@ -0,0 +1,4 @@ +--- +title: New `gitlab:workhorse:install` rake task +merge_request: 6574 +author: diff --git a/doc/install/installation.md b/doc/install/installation.md index ee02d6024d9..25b186b63f7 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -175,7 +175,7 @@ We recommend using a PostgreSQL database. For MySQL check the ```bash sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" ``` - + 1. Create the `pg_trgm` extension (required for GitLab 8.6+): ```bash @@ -396,15 +396,25 @@ GitLab Shell is an SSH access and repository management software developed speci ### Install gitlab-workhorse -GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). -If you are not using Linux you may have to run `gmake` instead of -`make` below. +GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The +following command-line will install GitLab-Workhorse in `home/git/gitlab-workhorse` +which is the recommended location. - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git - cd gitlab-workhorse - sudo -u git -H git checkout v1.0.1 - sudo -u git -H make + cd /home/git/gitlab + + sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] RAILS_ENV=production + +You can specify a different Git repository by providing `GITLAB_WORKHORSE_REPO`: + + cd /home/git/gitlab + + sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] GITLAB_WORKHORSE_REPO=https://gitlab.com/gitlab-org/gitlab-ce.git RAILS_ENV=production + +You can specify a different version to use by providing `GITLAB_WORKHORSE_VERSION`: + + cd /home/git/gitlab + + sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] GITLAB_WORKHORSE_VERSION=0.8.1 RAILS_ENV=production ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index c0084d9d59c..8c0d3f78b55 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -166,7 +166,7 @@ See [smtp_settings.rb.sample] as an example. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab - + For Ubuntu 16.04.1 LTS: sudo systemctl daemon-reload diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 58761a129d4..5a09cd7ce41 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -5,42 +5,23 @@ namespace :gitlab do warn_user_is_not_gitlab default_version = Gitlab::Shell.version_required - default_version_tag = 'v' + default_version - args.with_defaults(tag: default_version_tag, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") + default_version_tag = "v#{default_version}" + args.with_defaults(tag: default_version_tag, repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git') - user = Gitlab.config.gitlab.user - home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home gitlab_url = Gitlab.config.gitlab.url # gitlab-shell requires a / at the end of the url gitlab_url += '/' unless gitlab_url.end_with?('/') target_dir = Gitlab.config.gitlab_shell.path - # Clone if needed - if File.directory?(target_dir) - Dir.chdir(target_dir) do - system(*%W(Gitlab.config.git.bin_path} fetch --tags --quiet)) - system(*%W(Gitlab.config.git.bin_path} checkout --quiet #{default_version_tag})) - end - else - system(*%W(#{Gitlab.config.git.bin_path} clone -- #{args.repo} #{target_dir})) - end + checkout_or_clone_tag(tag: default_version_tag, repo: args.repo, target_dir: target_dir) # Make sure we're on the right tag Dir.chdir(target_dir) do - # First try to checkout without fetching - # to avoid stalling tests if the Internet is down. - reseted = reset_to_commit(args) - - unless reseted - system(*%W(#{Gitlab.config.git.bin_path} fetch origin)) - reset_to_commit(args) - end - config = { - user: user, + user: Gitlab.config.gitlab.user, gitlab_url: gitlab_url, http_settings: {self_signed_cert: false}.stringify_keys, - auth_file: File.join(home_dir, ".ssh", "authorized_keys"), + auth_file: File.join(user_home, ".ssh", "authorized_keys"), redis: { bin: %x{which redis-cli}.chomp, namespace: "resque:gitlab" @@ -74,7 +55,7 @@ namespace :gitlab do # be an issue since it is more than likely that there are no "normal" # user accounts on a gitlab server). The alternative is for the admin to # install a ruby (1.9.3+) in the global path. - File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f| + File.open(File.join(user_home, ".ssh", "environment"), "w+") do |f| f.puts "PATH=#{ENV['PATH']}" end @@ -142,15 +123,4 @@ namespace :gitlab do puts "Quitting...".color(:red) exit 1 end - - def reset_to_commit(args) - tag, status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} describe -- #{args.tag})) - - unless status.zero? - tag, status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} describe -- origin/#{args.tag})) - end - - tag = tag.strip - system(*%W(#{Gitlab.config.git.bin_path} reset --hard #{tag})) - end end diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index 74be413423a..85c3a3a9b0f 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -70,7 +70,7 @@ namespace :gitlab do # Runs the given command # - # Returns nil if the command was not found + # Returns '' if the command was not found # Returns the output of the command otherwise # # see also #run_and_match @@ -137,4 +137,37 @@ namespace :gitlab do def repository_storage_paths_args Gitlab.config.repositories.storages.values end + + def user_home + Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home + end + + def checkout_or_clone_tag(tag:, repo:, target_dir:) + if Dir.exist?(target_dir) + Dir.chdir(target_dir) do + run_command(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) + run_command(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) + end + else + run_command(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) + end + + # Make sure we're on the right tag + Dir.chdir(target_dir) do + # First try to checkout without fetching + # to avoid stalling tests if the Internet is down. + reset_to_tag(tag) + end + end + + def reset_to_tag(tag_wanted) + tag, status = Gitlab::Popen.popen(%W[#{Gitlab.config.git.bin_path} describe -- #{tag_wanted}]) + + unless status.zero? + run_command(%W(#{Gitlab.config.git.bin_path} fetch origin)) + tag = run_command(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag_wanted}]) + end + + run_command(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag.strip}]) + end end diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake new file mode 100644 index 00000000000..5964cb83b89 --- /dev/null +++ b/lib/tasks/gitlab/workhorse.rake @@ -0,0 +1,23 @@ +namespace :gitlab do + namespace :workhorse do + desc "GitLab | Install or upgrade gitlab-workhorse" + task :install, [:dir] => :environment do |t, args| + warn_user_is_not_gitlab + unless args.dir.present? + abort "Please specify the directory where you want to install gitlab-workhorse:\n rake gitlab:workhorse:install[/home/git/gitlab-workhorse]" + end + + tag = "v#{ENV['GITLAB_WORKHORSE_VERSION'] || Gitlab::Workhorse.version}" + repo = ENV['GITLAB_WORKHORSE_REPO'] || 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' + + checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir) + + _, status = Gitlab::Popen.popen(%w[which gmake]) + command = status.zero? ? 'gmake' : 'make' + + Dir.chdir(args.dir) do + run_command([command]) + end + end + end +end diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index f40ee862df8..c0847bbd7f1 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -17,6 +17,10 @@ module StubConfiguration allow(Gitlab.config.gravatar).to receive_messages(messages) end + def stub_gitlab_workhorse_setting(messages) + allow(Gitlab.config.gitlab_workhorse).to receive_messages(messages) + end + def stub_incoming_email_setting(messages) allow(Gitlab.config.incoming_email).to receive_messages(messages) end diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb new file mode 100644 index 00000000000..2a2d4a39ba8 --- /dev/null +++ b/spec/tasks/gitlab/task_helpers_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:workhorse namespace rake task' do + before :all do + Rake.application.rake_require 'tasks/gitlab/task_helpers' + + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + describe '#checkout_or_clone_tag' do + let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' } + let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s } + let(:tag) { 'v1.1.0' } + before do + FileUtils.rm_rf(clone_path) + allow_any_instance_of(Object).to receive(:run_command) + expect_any_instance_of(Object).to receive(:reset_to_tag).with(tag) + end + + after do + FileUtils.rm_rf(clone_path) + end + + context 'target_dir does not exist' do + it 'clones the repo, retrieve the tag from origin, and checkout the tag' do + expect(Dir).to receive(:chdir).and_call_original + expect_any_instance_of(Object). + to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}]) { FileUtils.mkdir_p(clone_path) } # Fake the cloning + + checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) + end + end + + context 'target_dir exists' do + before do + FileUtils.mkdir_p(clone_path) + end + + it 'fetch and checkout the tag' do + expect(Dir).to receive(:chdir).twice.and_call_original + expect_any_instance_of(Object). + to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) + expect_any_instance_of(Object). + to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) + + checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) + end + end + end + + describe '#reset_to_tag' do + let(:tag) { 'v1.1.0' } + before do + expect_any_instance_of(Object). + to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag}]) + end + + context 'when the tag is not checked out locally' do + before do + expect(Gitlab::Popen). + to receive(:popen).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_return(['', 42]) + end + + it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do + expect_any_instance_of(Object). + to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} fetch origin]) + expect_any_instance_of(Object). + to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag}]).and_return(tag) + + reset_to_tag(tag) + end + end + + context 'when the tag is checked out locally' do + before do + expect(Gitlab::Popen). + to receive(:popen).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_return([tag, 0]) + end + + it 'resets --hard to the given tag' do + reset_to_tag(tag) + end + end + end +end diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb new file mode 100644 index 00000000000..c8ad004282a --- /dev/null +++ b/spec/tasks/gitlab/workhorse_rake_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:workhorse namespace rake task' do + before :all do + Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/workhorse' + + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + def run_rake_task(task_name, *args) + Rake::Task[task_name].reenable + Rake.application.invoke_task("#{task_name}[#{args.join(',')}]") + end + + describe 'install' do + let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' } + let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s } + let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" } + before do + # avoid writing task output to spec progress + allow($stdout).to receive :write + allow(ENV).to receive(:[]) + end + + context 'no dir given' do + it 'aborts and display a help message' do + expect { run_rake_task('gitlab:workhorse:install') }.to raise_error /Please specify the directory where you want to install gitlab-workhorse/ + end + end + + context 'when an underlying Git command fail' do + it 'aborts and display a help message' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).and_raise 'Git error' + + expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error' + end + end + + describe 'checkout or clone' do + before do + expect(Dir).to receive(:chdir).with(clone_path) + end + + it 'calls checkout_or_clone_tag with the right arguments' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + + context 'given a specific repo' do + before do + expect(ENV).to receive(:[]).with('GITLAB_WORKHORSE_REPO').and_return('https://gitlab.com/user1/gitlab-workhorse.git') + end + + it 'calls checkout_or_clone_tag with the given repo' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).with(tag: tag, repo: 'https://gitlab.com/user1/gitlab-workhorse.git', target_dir: clone_path) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + end + + context 'given a specific version' do + before do + allow(ENV).to receive(:[]).with('GITLAB_WORKHORSE_VERSION').and_return('42.42.0') + end + + it 'calls checkout_or_clone_tag with the given repo' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).with(tag: 'v42.42.0', repo: repo, target_dir: clone_path) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + end + end + + describe 'gmake/make' do + before do + FileUtils.mkdir_p(clone_path) + expect(Dir).to receive(:chdir).with(clone_path).and_call_original + end + + context 'gmake is available' do + before do + expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) + allow_any_instance_of(Object).to receive(:run_command).with(['gmake']).and_return(true) + end + + it 'calls gmake in the gitlab-workhorse directory' do + expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0]) + expect_any_instance_of(Object).to receive(:run_command).with(['gmake']).and_return(true) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + end + + context 'gmake is not available' do + before do + expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) + allow_any_instance_of(Object).to receive(:run_command).with(['make']).and_return(true) + end + + it 'calls make in the gitlab-workhorse directory' do + expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42]) + expect_any_instance_of(Object).to receive(:run_command).with(['make']).and_return(true) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + end + end + end +end -- cgit v1.2.1 From a9c250eaddf758f99ac8c868dc86f4df0cc157f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 14:19:09 +0100 Subject: Add #run_command! to task helpers to raise a TaskFailedError if status is not 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/install/installation.md | 2 +- doc/update/8.14-to-8.15.md | 202 +++++++++++++++++++++++++++++++ lib/tasks/gitlab/task_helpers.rake | 36 ++++-- lib/tasks/gitlab/workhorse.rake | 2 +- spec/tasks/gitlab/task_helpers_spec.rb | 22 ++-- spec/tasks/gitlab/workhorse_rake_spec.rb | 8 +- 6 files changed, 246 insertions(+), 26 deletions(-) create mode 100644 doc/update/8.14-to-8.15.md diff --git a/doc/install/installation.md b/doc/install/installation.md index 25b186b63f7..de650f0900d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -408,7 +408,7 @@ You can specify a different Git repository by providing `GITLAB_WORKHORSE_REPO`: cd /home/git/gitlab - sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] GITLAB_WORKHORSE_REPO=https://gitlab.com/gitlab-org/gitlab-ce.git RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] GITLAB_WORKHORSE_REPO=https://example.com/gitlab-workhorse.git RAILS_ENV=production You can specify a different version to use by providing `GITLAB_WORKHORSE_VERSION`: diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md new file mode 100644 index 00000000000..9e6a3b45394 --- /dev/null +++ b/doc/update/8.14-to-8.15.md @@ -0,0 +1,202 @@ +# From 8.14 to 8.15 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +We will continue supporting Ruby < 2.3 for the time being but we recommend you +upgrade to Ruby 2.3 if you're running a source installation, as this is the same +version that ships with our Omnibus package. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz +echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz +cd ruby-2.3.1 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-15-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-15-stable-ee +``` + +### 5. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v4.0.0 +``` + +### 6. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] RAILS_ENV=production +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 8. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-13-stable:config/gitlab.yml.example origin/8-15-stable:config/gitlab.yml.example +``` + +#### Git configuration + +Configure Git to generate packfile bitmaps (introduced in Git 2.0) on +the GitLab server during `git gc`. + +```sh +sudo -u git -H git config --global repack.writeBitmaps true +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-13-stable:lib/support/nginx/gitlab-ssl origin/8-15-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-13-stable:lib/support/nginx/gitlab origin/8-15-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-15-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-15-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.14) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.13 to 8.14](8.13-to-8.14.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index 85c3a3a9b0f..c0759b96602 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -1,4 +1,5 @@ module Gitlab + class TaskFailedError < StandardError; end class TaskAbortedByUserError < StandardError; end end @@ -81,6 +82,18 @@ namespace :gitlab do '' # if the command does not exist, return an empty string end + # Runs the given command and raise a Gitlab::TaskFailedError exception if + # the command does not exit with 0 + # + # Returns the output of the command otherwise + def run_command!(command) + output, status = Gitlab::Popen.popen(command) + + raise Gitlab::TaskFailedError unless status.zero? + + output + end + def uid_for(user_name) run_command(%W(id -u #{user_name})).chomp.to_i end @@ -145,11 +158,11 @@ namespace :gitlab do def checkout_or_clone_tag(tag:, repo:, target_dir:) if Dir.exist?(target_dir) Dir.chdir(target_dir) do - run_command(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) - run_command(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) + run_command!(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) + run_command!(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) end else - run_command(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) + run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) end # Make sure we're on the right tag @@ -161,13 +174,18 @@ namespace :gitlab do end def reset_to_tag(tag_wanted) - tag, status = Gitlab::Popen.popen(%W[#{Gitlab.config.git.bin_path} describe -- #{tag_wanted}]) + tag = + begin + run_command!(%W[#{Gitlab.config.git.bin_path} describe -- #{tag_wanted}]) + rescue Gitlab::TaskFailedError + run_command!(%W[#{Gitlab.config.git.bin_path} fetch origin]) + run_command!(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag_wanted}]) + end - unless status.zero? - run_command(%W(#{Gitlab.config.git.bin_path} fetch origin)) - tag = run_command(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag_wanted}]) + if tag + run_command!(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag.strip}]) + else + raise Gitlab::TaskFailedError end - - run_command(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag.strip}]) end end diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake index 5964cb83b89..563744dd0c7 100644 --- a/lib/tasks/gitlab/workhorse.rake +++ b/lib/tasks/gitlab/workhorse.rake @@ -16,7 +16,7 @@ namespace :gitlab do command = status.zero? ? 'gmake' : 'make' Dir.chdir(args.dir) do - run_command([command]) + run_command!([command]) end end end diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb index 2a2d4a39ba8..dccb3b4cf9a 100644 --- a/spec/tasks/gitlab/task_helpers_spec.rb +++ b/spec/tasks/gitlab/task_helpers_spec.rb @@ -15,7 +15,7 @@ describe 'gitlab:workhorse namespace rake task' do let(:tag) { 'v1.1.0' } before do FileUtils.rm_rf(clone_path) - allow_any_instance_of(Object).to receive(:run_command) + allow_any_instance_of(Object).to receive(:run_command!) expect_any_instance_of(Object).to receive(:reset_to_tag).with(tag) end @@ -27,7 +27,7 @@ describe 'gitlab:workhorse namespace rake task' do it 'clones the repo, retrieve the tag from origin, and checkout the tag' do expect(Dir).to receive(:chdir).and_call_original expect_any_instance_of(Object). - to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}]) { FileUtils.mkdir_p(clone_path) } # Fake the cloning + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}]) { FileUtils.mkdir_p(clone_path) } # Fake the cloning checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) end @@ -41,9 +41,9 @@ describe 'gitlab:workhorse namespace rake task' do it 'fetch and checkout the tag' do expect(Dir).to receive(:chdir).twice.and_call_original expect_any_instance_of(Object). - to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) expect_any_instance_of(Object). - to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) end @@ -54,20 +54,20 @@ describe 'gitlab:workhorse namespace rake task' do let(:tag) { 'v1.1.0' } before do expect_any_instance_of(Object). - to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag}]) + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag}]) end context 'when the tag is not checked out locally' do before do - expect(Gitlab::Popen). - to receive(:popen).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_return(['', 42]) + expect_any_instance_of(Object). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError) end it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do expect_any_instance_of(Object). - to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} fetch origin]) + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} fetch origin]) expect_any_instance_of(Object). - to receive(:run_command).with(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag}]).and_return(tag) + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag}]).and_return(tag) reset_to_tag(tag) end @@ -75,8 +75,8 @@ describe 'gitlab:workhorse namespace rake task' do context 'when the tag is checked out locally' do before do - expect(Gitlab::Popen). - to receive(:popen).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_return([tag, 0]) + expect_any_instance_of(Object). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_return(tag) end it 'resets --hard to the given tag' do diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb index c8ad004282a..87bc1b128bf 100644 --- a/spec/tasks/gitlab/workhorse_rake_spec.rb +++ b/spec/tasks/gitlab/workhorse_rake_spec.rb @@ -88,12 +88,12 @@ describe 'gitlab:workhorse namespace rake task' do context 'gmake is available' do before do expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) - allow_any_instance_of(Object).to receive(:run_command).with(['gmake']).and_return(true) + allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) end it 'calls gmake in the gitlab-workhorse directory' do expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0]) - expect_any_instance_of(Object).to receive(:run_command).with(['gmake']).and_return(true) + expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) run_rake_task('gitlab:workhorse:install', clone_path) end @@ -102,12 +102,12 @@ describe 'gitlab:workhorse namespace rake task' do context 'gmake is not available' do before do expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) - allow_any_instance_of(Object).to receive(:run_command).with(['make']).and_return(true) + allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) end it 'calls make in the gitlab-workhorse directory' do expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42]) - expect_any_instance_of(Object).to receive(:run_command).with(['make']).and_return(true) + expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) run_rake_task('gitlab:workhorse:install', clone_path) end -- cgit v1.2.1 From 5b052605b7eb7281f88040962e530ac80bbe3774 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 30 Nov 2016 10:13:47 +0100 Subject: Extend code review docs with chapter about the right balance --- doc/development/code_review.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/doc/development/code_review.md b/doc/development/code_review.md index c5c23b5c0b8..709cefcb5c6 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -70,10 +70,36 @@ experience, refactors the existing code). Then: - After a round of line notes, it can be helpful to post a summary note such as "LGTM :thumbsup:", or "Just a couple things to address." - Avoid accepting a merge request before the build succeeds. Of course, "Merge - When Build Succeeds" (MWBS) is fine. + When Pipeline Succeeds" is fine. - If you set the MR to "Merge When Build Succeeds", you should take over subsequent revisions for anything that would be spotted after that. +## The right balance + +One of the most difficult things during the code review is finding the right +balance in how deep the reviewer can interfere with the code created by a +reviewee. + +- Learning how to find the right balance takes time, that is why we have + minibosses that become merge request endbosses after some time spent on + reviewing merge requests. +- Finding bugs and improving code style is important, but thinking about good + design is important as well. Building abstractions and good design is what + makes it possible to hide complexity and is a leverage for the future work. +- Asking reviewee to change the design sometimes means the complete rewrite of + the contributed code. It is usually a good idea to ask other merge request + endboss before doing it, but have the courage to do it when you believe it is + important. +- There is a difference in doing things right and doing things right now. + Ideally, we should do the former, but in the real world we need the latter as + well. The good example is a security fix which should be released as soon as + possible. Asking reviewee to do the major refactoring in the merge request + that is an urgent fix should be avoided. +- Doing things well today is usually better than doing something perfectly + tomorrow. Shipping a kludge today is usually worse than doing something well + tomorrow. When you are not able to find the right balance, ask other people + about their opinion. + ## Credits Largely based on the [thoughtbot code review guide]. -- cgit v1.2.1 From 0fbb5a86dbe054af91c20d36697fda273445dd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ger=C5=91?= Date: Sat, 29 Oct 2016 12:51:01 +0200 Subject: Add Human Readable Timestamp to backup tar file --- .../23718-backup-rake-task-human-readable.yml | 4 +++ doc/raketasks/backup_restore.md | 2 +- lib/backup/manager.rb | 18 ++++++++----- spec/tasks/gitlab/backup_rake_spec.rb | 31 ++++++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/23718-backup-rake-task-human-readable.yml diff --git a/changelogs/unreleased/23718-backup-rake-task-human-readable.yml b/changelogs/unreleased/23718-backup-rake-task-human-readable.yml new file mode 100644 index 00000000000..2e7583244ac --- /dev/null +++ b/changelogs/unreleased/23718-backup-rake-task-human-readable.yml @@ -0,0 +1,4 @@ +--- +title: Add Human Readable format for rake backup +merge_request: 7188 +author: David Gerő diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 17485b11c09..f42bb6a81a2 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -353,7 +353,7 @@ restore: ```shell # This command will overwrite the contents of your GitLab database! -sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186 +sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186_2014_02_27 ``` Restart and check GitLab: diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 0dfffaf0bc6..96c20100541 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -14,7 +14,7 @@ module Backup s[:gitlab_version] = Gitlab::VERSION s[:tar_version] = tar_version s[:skipped] = ENV["SKIP"] - tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar" + tar_file = s[:backup_created_at].strftime('%s_%Y_%m_%d') + '_gitlab_backup.tar' Dir.chdir(Gitlab.config.backup.path) do File.open("#{Gitlab.config.backup.path}/backup_information.yml", @@ -83,10 +83,14 @@ module Backup Dir.chdir(Gitlab.config.backup.path) do file_list = Dir.glob('*_gitlab_backup.tar') - file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } - file_list.sort.each do |timestamp| - if Time.at(timestamp) < (Time.now - keep_time) - if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar)) + file_list.map! do |path_string| + if path_string =~ /(\d+)(?:_\d{4}_\d{2}_\d{2})?_gitlab_backup\.tar/ + { timestamp: $1.to_i, path: path_string } + end + end + file_list.sort.each do |file| + if Time.at(file[:timestamp]) < (Time.now - keep_time) + if Kernel.system(*%W(rm #{file[:path]})) removed += 1 end end @@ -103,7 +107,7 @@ module Backup Dir.chdir(Gitlab.config.backup.path) # check for existing backups in the backup dir - file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } + file_list = Dir.glob("*_gitlab_backup.tar") puts "no backups found" if file_list.count == 0 if file_list.count > 1 && ENV["BACKUP"].nil? @@ -112,7 +116,7 @@ module Backup exit 1 end - tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar") + tar_file = ENV["BACKUP"].nil? ? file_list.first : file_list.grep(ENV['BACKUP']).first unless File.exist?(tar_file) puts "The specified backup doesn't exist!" diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 287d83344db..2b244c13961 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -333,4 +333,35 @@ describe 'gitlab:app namespace rake task' do expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end end + + describe "Human Readable Backup Name" do + def tars_glob + Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) + end + + before :all do + @origin_cd = Dir.pwd + + reenable_backup_sub_tasks + + FileUtils.rm tars_glob + + # Redirect STDOUT and run the rake task + orig_stdout = $stdout + $stdout = StringIO.new + run_rake_task('gitlab:backup:create') + $stdout = orig_stdout + + @backup_tar = tars_glob.first + end + + after :all do + FileUtils.rm(@backup_tar) + Dir.chdir @origin_cd + end + + it 'name has human readable time' do + expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_gitlab_backup.tar$/) + end + end end # gitlab:app namespace -- cgit v1.2.1 From 34053a49703613d08bab7010a970b0ea8096ebad Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 29 Nov 2016 16:40:03 +0000 Subject: Fixed GFM autocomplete regex Without this fix it is possible that the autocomplete popup will stay open with the very first match, no matter if there is a match further in the string. Closes #25119 --- app/assets/javascripts/gfm_auto_complete.js.es6 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 89fe13b7a45..10769b7fd4f 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -59,12 +59,13 @@ // Tweaked to commands to start without a space only if char before is a non-word character // https://github.com/ichord/At.js var _a, _y, regexp, match; + subtext = subtext.split(' ').pop(); flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); _a = decodeURI("%C3%80"); _y = decodeURI("%C3%BF"); - regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); + regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "(?!\\W)([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); match = regexp.exec(subtext); -- cgit v1.2.1 From b193e8497444a19e4ea541f73f82eb7e21aa8879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 29 Nov 2016 19:21:25 +0100 Subject: Move task helpers to a module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/install/installation.md | 8 +- doc/update/8.14-to-8.15.md | 2 +- lib/tasks/gitlab/helpers.rake | 8 + lib/tasks/gitlab/task_helpers.rake | 191 --------------------- lib/tasks/gitlab/task_helpers.rb | 190 ++++++++++++++++++++ lib/tasks/gitlab/workhorse.rake | 2 +- spec/rake_helper.rb | 2 +- spec/support/rake_helpers.rb | 4 +- spec/support/stub_configuration.rb | 4 - spec/tasks/gitlab/backup_rake_spec.rb | 2 +- .../gitlab/mail_google_schema_whitelisting.rb | 2 +- spec/tasks/gitlab/task_helpers_spec.rb | 89 +++++----- spec/tasks/gitlab/users_rake_spec.rb | 2 +- spec/tasks/gitlab/workhorse_rake_spec.rb | 16 +- 14 files changed, 262 insertions(+), 260 deletions(-) create mode 100644 lib/tasks/gitlab/helpers.rake delete mode 100644 lib/tasks/gitlab/task_helpers.rake create mode 100644 lib/tasks/gitlab/task_helpers.rb diff --git a/doc/install/installation.md b/doc/install/installation.md index de650f0900d..77adb4c9f7b 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -397,24 +397,24 @@ GitLab Shell is an SSH access and repository management software developed speci ### Install gitlab-workhorse GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The -following command-line will install GitLab-Workhorse in `home/git/gitlab-workhorse` +following command-line will install GitLab-Workhorse in `/home/git/gitlab-workhorse` which is the recommended location. cd /home/git/gitlab - sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] RAILS_ENV=production + sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production You can specify a different Git repository by providing `GITLAB_WORKHORSE_REPO`: cd /home/git/gitlab - sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] GITLAB_WORKHORSE_REPO=https://example.com/gitlab-workhorse.git RAILS_ENV=production + sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" GITLAB_WORKHORSE_REPO=https://example.com/gitlab-workhorse.git RAILS_ENV=production You can specify a different version to use by providing `GITLAB_WORKHORSE_VERSION`: cd /home/git/gitlab - sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] GITLAB_WORKHORSE_VERSION=0.8.1 RAILS_ENV=production + sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" GITLAB_WORKHORSE_VERSION=0.8.1 RAILS_ENV=production ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md index 9e6a3b45394..576b943b98c 100644 --- a/doc/update/8.14-to-8.15.md +++ b/doc/update/8.14-to-8.15.md @@ -82,7 +82,7 @@ Install and compile gitlab-workhorse. This requires GitLab 8.1. ```bash -sudo -u git -H bundle exec rake gitlab:workhorse:install[/home/git/gitlab-workhorse] RAILS_ENV=production +sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production ``` ### 7. Install libs, migrations, etc. diff --git a/lib/tasks/gitlab/helpers.rake b/lib/tasks/gitlab/helpers.rake new file mode 100644 index 00000000000..dd2d5861481 --- /dev/null +++ b/lib/tasks/gitlab/helpers.rake @@ -0,0 +1,8 @@ +require 'tasks/gitlab/task_helpers' + +# Prevent StateMachine warnings from outputting during a cron task +StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON'] + +namespace :gitlab do + include Gitlab::TaskHelpers +end diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake deleted file mode 100644 index c0759b96602..00000000000 --- a/lib/tasks/gitlab/task_helpers.rake +++ /dev/null @@ -1,191 +0,0 @@ -module Gitlab - class TaskFailedError < StandardError; end - class TaskAbortedByUserError < StandardError; end -end - -require 'rainbow/ext/string' - -# Prevent StateMachine warnings from outputting during a cron task -StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON'] - -namespace :gitlab do - - # Ask if the user wants to continue - # - # Returns "yes" the user chose to continue - # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue - def ask_to_continue - answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no}) - raise Gitlab::TaskAbortedByUserError unless answer == "yes" - end - - # Check which OS is running - # - # It will primarily use lsb_relase to determine the OS. - # It has fallbacks to Debian, SuSE, OS X and systems running systemd. - def os_name - os_name = run_command(%W(lsb_release -irs)) - os_name ||= if File.readable?('/etc/system-release') - File.read('/etc/system-release') - end - os_name ||= if File.readable?('/etc/debian_version') - debian_version = File.read('/etc/debian_version') - "Debian #{debian_version}" - end - os_name ||= if File.readable?('/etc/SuSE-release') - File.read('/etc/SuSE-release') - end - os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion)) - "Mac OS X #{os_x_version}" - end - os_name ||= if File.readable?('/etc/os-release') - File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1] - end - os_name.try(:squish!) - end - - # Prompt the user to input something - # - # message - the message to display before input - # choices - array of strings of acceptable answers or nil for any answer - # - # Returns the user's answer - def prompt(message, choices = nil) - begin - print(message) - answer = STDIN.gets.chomp - end while choices.present? && !choices.include?(answer) - answer - end - - # Runs the given command and matches the output against the given pattern - # - # Returns nil if nothing matched - # Returns the MatchData if the pattern matched - # - # see also #run_command - # see also String#match - def run_and_match(command, regexp) - run_command(command).try(:match, regexp) - end - - # Runs the given command - # - # Returns '' if the command was not found - # Returns the output of the command otherwise - # - # see also #run_and_match - def run_command(command) - output, _ = Gitlab::Popen.popen(command) - output - rescue Errno::ENOENT - '' # if the command does not exist, return an empty string - end - - # Runs the given command and raise a Gitlab::TaskFailedError exception if - # the command does not exit with 0 - # - # Returns the output of the command otherwise - def run_command!(command) - output, status = Gitlab::Popen.popen(command) - - raise Gitlab::TaskFailedError unless status.zero? - - output - end - - def uid_for(user_name) - run_command(%W(id -u #{user_name})).chomp.to_i - end - - def gid_for(group_name) - begin - Etc.getgrnam(group_name).gid - rescue ArgumentError # no group - "group #{group_name} doesn't exist" - end - end - - def warn_user_is_not_gitlab - unless @warned_user_not_gitlab - gitlab_user = Gitlab.config.gitlab.user - current_user = run_command(%W(whoami)).chomp - unless current_user == gitlab_user - puts " Warning ".color(:black).background(:yellow) - puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing." - puts " Things may work\/fail for the wrong reasons." - puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}." - puts "" - end - @warned_user_not_gitlab = true - end - end - - # Tries to configure git itself - # - # Returns true if all subcommands were successfull (according to their exit code) - # Returns false if any or all subcommands failed. - def auto_fix_git_config(options) - if !@warned_user_not_gitlab - command_success = options.map do |name, value| - system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value})) - end - - command_success.all? - else - false - end - end - - def all_repos - Gitlab.config.repositories.storages.each do |name, path| - IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| - find.each_line do |path| - yield path.chomp - end - end - end - end - - def repository_storage_paths_args - Gitlab.config.repositories.storages.values - end - - def user_home - Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home - end - - def checkout_or_clone_tag(tag:, repo:, target_dir:) - if Dir.exist?(target_dir) - Dir.chdir(target_dir) do - run_command!(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) - run_command!(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) - end - else - run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) - end - - # Make sure we're on the right tag - Dir.chdir(target_dir) do - # First try to checkout without fetching - # to avoid stalling tests if the Internet is down. - reset_to_tag(tag) - end - end - - def reset_to_tag(tag_wanted) - tag = - begin - run_command!(%W[#{Gitlab.config.git.bin_path} describe -- #{tag_wanted}]) - rescue Gitlab::TaskFailedError - run_command!(%W[#{Gitlab.config.git.bin_path} fetch origin]) - run_command!(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag_wanted}]) - end - - if tag - run_command!(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag.strip}]) - else - raise Gitlab::TaskFailedError - end - end -end diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb new file mode 100644 index 00000000000..e128738b5f8 --- /dev/null +++ b/lib/tasks/gitlab/task_helpers.rb @@ -0,0 +1,190 @@ +require 'rainbow/ext/string' + +module Gitlab + TaskFailedError = Class.new(StandardError) + TaskAbortedByUserError = Class.new(StandardError) + + module TaskHelpers + # Ask if the user wants to continue + # + # Returns "yes" the user chose to continue + # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue + def ask_to_continue + answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no}) + raise Gitlab::TaskAbortedByUserError unless answer == "yes" + end + + # Check which OS is running + # + # It will primarily use lsb_relase to determine the OS. + # It has fallbacks to Debian, SuSE, OS X and systems running systemd. + def os_name + os_name = run_command(%W(lsb_release -irs)) + os_name ||= if File.readable?('/etc/system-release') + File.read('/etc/system-release') + end + os_name ||= if File.readable?('/etc/debian_version') + debian_version = File.read('/etc/debian_version') + "Debian #{debian_version}" + end + os_name ||= if File.readable?('/etc/SuSE-release') + File.read('/etc/SuSE-release') + end + os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion)) + "Mac OS X #{os_x_version}" + end + os_name ||= if File.readable?('/etc/os-release') + File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1] + end + os_name.try(:squish!) + end + + # Prompt the user to input something + # + # message - the message to display before input + # choices - array of strings of acceptable answers or nil for any answer + # + # Returns the user's answer + def prompt(message, choices = nil) + begin + print(message) + answer = STDIN.gets.chomp + end while choices.present? && !choices.include?(answer) + answer + end + + # Runs the given command and matches the output against the given pattern + # + # Returns nil if nothing matched + # Returns the MatchData if the pattern matched + # + # see also #run_command + # see also String#match + def run_and_match(command, regexp) + run_command(command).try(:match, regexp) + end + + # Runs the given command + # + # Returns '' if the command was not found + # Returns the output of the command otherwise + # + # see also #run_and_match + def run_command(command) + output, _ = Gitlab::Popen.popen(command) + output + rescue Errno::ENOENT + '' # if the command does not exist, return an empty string + end + + # Runs the given command and raises a Gitlab::TaskFailedError exception if + # the command does not exit with 0 + # + # Returns the output of the command otherwise + def run_command!(command) + output, status = Gitlab::Popen.popen(command) + + raise Gitlab::TaskFailedError unless status.zero? + + output + end + + def uid_for(user_name) + run_command(%W(id -u #{user_name})).chomp.to_i + end + + def gid_for(group_name) + begin + Etc.getgrnam(group_name).gid + rescue ArgumentError # no group + "group #{group_name} doesn't exist" + end + end + + def warn_user_is_not_gitlab + unless @warned_user_not_gitlab + gitlab_user = Gitlab.config.gitlab.user + current_user = run_command(%W(whoami)).chomp + unless current_user == gitlab_user + puts " Warning ".color(:black).background(:yellow) + puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing." + puts " Things may work\/fail for the wrong reasons." + puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}." + puts "" + end + @warned_user_not_gitlab = true + end + end + + # Tries to configure git itself + # + # Returns true if all subcommands were successfull (according to their exit code) + # Returns false if any or all subcommands failed. + def auto_fix_git_config(options) + if !@warned_user_not_gitlab + command_success = options.map do |name, value| + system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value})) + end + + command_success.all? + else + false + end + end + + def all_repos + Gitlab.config.repositories.storages.each do |name, path| + IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| + find.each_line do |path| + yield path.chomp + end + end + end + end + + def repository_storage_paths_args + Gitlab.config.repositories.storages.values + end + + def user_home + Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home + end + + def checkout_or_clone_tag(tag:, repo:, target_dir:) + if Dir.exist?(target_dir) + checkout_tag(tag, target_dir) + else + clone_repo(repo, target_dir) + end + + reset_to_tag(tag, target_dir) + end + + def clone_repo(repo, target_dir) + run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) + end + + def checkout_tag(tag, target_dir) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --tags --quiet]) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{tag}]) + end + + def reset_to_tag(tag_wanted, target_dir) + tag = + begin + # First try to checkout without fetching + # to avoid stalling tests if the Internet is down. + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}]) + rescue Gitlab::TaskFailedError + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin]) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}]) + end + + if tag + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}]) + else + raise Gitlab::TaskFailedError + end + end + end +end diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake index 563744dd0c7..46bd0bf2e7b 100644 --- a/lib/tasks/gitlab/workhorse.rake +++ b/lib/tasks/gitlab/workhorse.rake @@ -4,7 +4,7 @@ namespace :gitlab do task :install, [:dir] => :environment do |t, args| warn_user_is_not_gitlab unless args.dir.present? - abort "Please specify the directory where you want to install gitlab-workhorse:\n rake gitlab:workhorse:install[/home/git/gitlab-workhorse]" + abort %(Please specify the directory where you want to install gitlab-workhorse:\n rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]") end tag = "v#{ENV['GITLAB_WORKHORSE_VERSION'] || Gitlab::Workhorse.version}" diff --git a/spec/rake_helper.rb b/spec/rake_helper.rb index 9b5b4bf9fea..298a520f5ca 100644 --- a/spec/rake_helper.rb +++ b/spec/rake_helper.rb @@ -8,7 +8,7 @@ RSpec.configure do |config| config.before(:all) do $stdout = StringIO.new - Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/helpers' Rake::Task.define_task :environment end diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb index 52d80c69835..4a8158ed79b 100644 --- a/spec/support/rake_helpers.rb +++ b/spec/support/rake_helpers.rb @@ -1,7 +1,7 @@ module RakeHelpers - def run_rake_task(task_name) + def run_rake_task(task_name, *args) Rake::Task[task_name].reenable - Rake.application.invoke_task task_name + Rake.application.invoke_task("#{task_name}[#{args.join(',')}]") end def stub_warn_user_is_not_gitlab diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index c0847bbd7f1..f40ee862df8 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -17,10 +17,6 @@ module StubConfiguration allow(Gitlab.config.gravatar).to receive_messages(messages) end - def stub_gitlab_workhorse_setting(messages) - allow(Gitlab.config.gitlab_workhorse).to receive_messages(messages) - end - def stub_incoming_email_setting(messages) allow(Gitlab.config.incoming_email).to receive_messages(messages) end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 287d83344db..ecbfc236d3d 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -5,7 +5,7 @@ describe 'gitlab:app namespace rake task' do let(:enable_registry) { true } before :all do - Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/helpers' Rake.application.rake_require 'tasks/gitlab/backup' Rake.application.rake_require 'tasks/gitlab/shell' Rake.application.rake_require 'tasks/gitlab/db' diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb index 37feb5e6faf..80fc8c48fed 100644 --- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb +++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb @@ -3,7 +3,7 @@ require 'rake' describe 'gitlab:mail_google_schema_whitelisting rake task' do before :all do - Rake.application.rake_require "tasks/gitlab/task_helpers" + Rake.application.rake_require "tasks/gitlab/helpers" Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting" # empty task as env is already loaded Rake::Task.define_task :environment diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb index dccb3b4cf9a..86e42d845ce 100644 --- a/spec/tasks/gitlab/task_helpers_spec.rb +++ b/spec/tasks/gitlab/task_helpers_spec.rb @@ -1,86 +1,95 @@ require 'spec_helper' -require 'rake' +require 'tasks/gitlab/task_helpers' -describe 'gitlab:workhorse namespace rake task' do - before :all do - Rake.application.rake_require 'tasks/gitlab/task_helpers' +class TestHelpersTest + include Gitlab::TaskHelpers +end - # empty task as env is already loaded - Rake::Task.define_task :environment - end +describe Gitlab::TaskHelpers do + subject { TestHelpersTest.new } + + let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' } + let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s } + let(:tag) { 'v1.1.0' } describe '#checkout_or_clone_tag' do - let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' } - let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s } - let(:tag) { 'v1.1.0' } before do - FileUtils.rm_rf(clone_path) - allow_any_instance_of(Object).to receive(:run_command!) - expect_any_instance_of(Object).to receive(:reset_to_tag).with(tag) - end - - after do - FileUtils.rm_rf(clone_path) + allow(subject).to receive(:run_command!) + expect(subject).to receive(:reset_to_tag).with(tag, clone_path) end context 'target_dir does not exist' do it 'clones the repo, retrieve the tag from origin, and checkout the tag' do - expect(Dir).to receive(:chdir).and_call_original - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}]) { FileUtils.mkdir_p(clone_path) } # Fake the cloning + expect(subject).to receive(:clone_repo).with(repo, clone_path) - checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) + subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) end end context 'target_dir exists' do before do - FileUtils.mkdir_p(clone_path) + expect(Dir).to receive(:exist?).and_return(true) end it 'fetch and checkout the tag' do - expect(Dir).to receive(:chdir).twice.and_call_original - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} fetch --tags --quiet]) - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} checkout --quiet #{tag}]) + expect(subject).to receive(:checkout_tag).with(tag, clone_path) - checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) + subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) end end end + describe '#clone_repo' do + it 'clones the repo in the target dir' do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}]) + + subject.clone_repo(repo, clone_path) + end + end + + describe '#checkout_tag' do + it 'clones the repo in the target dir' do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --tags --quiet]) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}]) + + subject.checkout_tag(tag, clone_path) + end + end + describe '#reset_to_tag' do let(:tag) { 'v1.1.0' } before do - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} reset --hard #{tag}]) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}]) end context 'when the tag is not checked out locally' do before do - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError) end it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} fetch origin]) - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} describe -- origin/#{tag}]).and_return(tag) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch origin]) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- origin/#{tag}]).and_return(tag) - reset_to_tag(tag) + subject.reset_to_tag(tag, clone_path) end end context 'when the tag is checked out locally' do before do - expect_any_instance_of(Object). - to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} describe -- #{tag}]).and_return(tag) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_return(tag) end it 'resets --hard to the given tag' do - reset_to_tag(tag) + subject.reset_to_tag(tag, clone_path) end end end diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb index e6ebef82b78..972670e7f91 100644 --- a/spec/tasks/gitlab/users_rake_spec.rb +++ b/spec/tasks/gitlab/users_rake_spec.rb @@ -5,7 +5,7 @@ describe 'gitlab:users namespace rake task' do let(:enable_registry) { true } before :all do - Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/helpers' Rake.application.rake_require 'tasks/gitlab/users' # empty task as env is already loaded diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb index 87bc1b128bf..b695abce091 100644 --- a/spec/tasks/gitlab/workhorse_rake_spec.rb +++ b/spec/tasks/gitlab/workhorse_rake_spec.rb @@ -1,18 +1,8 @@ -require 'spec_helper' -require 'rake' +require 'rake_helper' describe 'gitlab:workhorse namespace rake task' do before :all do - Rake.application.rake_require 'tasks/gitlab/task_helpers' Rake.application.rake_require 'tasks/gitlab/workhorse' - - # empty task as env is already loaded - Rake::Task.define_task :environment - end - - def run_rake_task(task_name, *args) - Rake::Task[task_name].reenable - Rake.application.invoke_task("#{task_name}[#{args.join(',')}]") end describe 'install' do @@ -20,13 +10,13 @@ describe 'gitlab:workhorse namespace rake task' do let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s } let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" } before do - # avoid writing task output to spec progress - allow($stdout).to receive :write allow(ENV).to receive(:[]) end context 'no dir given' do it 'aborts and display a help message' do + # avoid writing task output to spec progress + allow($stderr).to receive :write expect { run_rake_task('gitlab:workhorse:install') }.to raise_error /Please specify the directory where you want to install gitlab-workhorse/ end end -- cgit v1.2.1 From dd5f71138ce98522b1324319fbd60f665b3d1337 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 28 Nov 2016 22:15:12 +0100 Subject: Grapify the files API --- doc/api/repository_files.md | 4 +- lib/api/files.rb | 153 +++++++++++++++++--------------------------- 2 files changed, 62 insertions(+), 95 deletions(-) diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 1bc6a24e914..b8c9eb2c9a8 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -60,7 +60,7 @@ Parameters: - `file_path` (required) - Full path to new file. Ex. lib/class.rb - `branch_name` (required) - The name of branch -- `encoding` (optional) - 'text' or 'base64'. Text is default. +- `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name - `content` (required) - File content @@ -89,7 +89,7 @@ Parameters: - `file_path` (required) - Full path to file. Ex. lib/class.rb - `branch_name` (required) - The name of branch -- `encoding` (optional) - 'text' or 'base64'. Text is default. +- `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name - `content` (required) - New file content diff --git a/lib/api/files.rb b/lib/api/files.rb index 96510e651a3..28f306e45f3 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -23,140 +23,107 @@ module API branch_name: attrs[:branch_name] } end + + params :simple_file_params do + requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb' + requires :branch_name, type: String, desc: 'The name of branch' + requires :commit_message, type: String, desc: 'Commit Message' + optional :author_email, type: String, desc: 'The email of the author' + optional :author_name, type: String, desc: 'The name of the author' + end + + params :extended_file_params do + use :simple_file_params + requires :content, type: String, desc: 'File content' + optional :encoding, type: String, values: %w[base64], desc: 'File encoding' + end end + params do + requires :id, type: String, desc: 'The project ID' + end resource :projects do - # Get file from repository - # File content is Base64 encoded - # - # Parameters: - # file_path (required) - The path to the file. Ex. lib/class.rb - # ref (required) - The name of branch, tag or commit - # - # Example Request: - # GET /projects/:id/repository/files - # - # Example response: - # { - # "file_name": "key.rb", - # "file_path": "app/models/key.rb", - # "size": 1476, - # "encoding": "base64", - # "content": "IyA9PSBTY2hlbWEgSW5mb3...", - # "ref": "master", - # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", - # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50", - # "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - # } - # + desc 'Get a file from repository' + params do + requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb' + requires :ref, type: String, desc: 'The name of branch, tag, or commit' + end get ":id/repository/files" do authorize! :download_code, user_project - required_attributes! [:file_path, :ref] - attrs = attributes_for_keys [:file_path, :ref] - ref = attrs.delete(:ref) - file_path = attrs.delete(:file_path) - - commit = user_project.commit(ref) - not_found! 'Commit' unless commit + commit = user_project.commit(params[:ref]) + not_found!('Commit') unless commit repo = user_project.repository - blob = repo.blob_at(commit.sha, file_path) + blob = repo.blob_at(commit.sha, params[:file_path]) + not_found!('File') unless blob - if blob - blob.load_all_data!(repo) - status(200) + blob.load_all_data!(repo) + status(200) - { - file_name: blob.name, - file_path: blob.path, - size: blob.size, - encoding: "base64", - content: Base64.strict_encode64(blob.data), - ref: ref, - blob_id: blob.id, - commit_id: commit.id, - last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id - } - else - not_found! 'File' - end + { + file_name: blob.name, + file_path: blob.path, + size: blob.size, + encoding: "base64", + content: Base64.strict_encode64(blob.data), + ref: params[:ref], + blob_id: blob.id, + commit_id: commit.id, + last_commit_id: repo.last_commit_for_path(commit.sha, params[:file_path]).id + } end - # Create new file in repository - # - # Parameters: - # file_path (required) - The path to new file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # POST /projects/:id/repository/files - # + desc 'Create new file in repository' + params do + use :extended_file_params + end post ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::CreateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(201) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end end - # Update existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # PUT /projects/:id/repository/files - # + desc 'Update existing file in repository' + params do + use :extended_file_params + end put ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::UpdateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else http_status = result[:http_status] || 400 render_api_error!(result[:message], http_status) end end - # Delete existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # DELETE /projects/:id/repository/files - # + desc 'Delete an existing file in repository' + params do + use :simple_file_params + end delete ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :commit_message, :author_email, :author_name] - result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::DeleteService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end -- cgit v1.2.1 From 2ce66c071fc7ab2b8ca881223321a3927ec7d61e Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 28 Nov 2016 19:16:15 +0100 Subject: API: Expose branch status --- changelogs/unreleased/api-branch-status.yml | 4 ++++ doc/api/branches.md | 5 +++++ lib/api/entities.rb | 6 +++++- spec/requests/api/branches_spec.rb | 11 +++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/api-branch-status.yml diff --git a/changelogs/unreleased/api-branch-status.yml b/changelogs/unreleased/api-branch-status.yml new file mode 100644 index 00000000000..c5763345a22 --- /dev/null +++ b/changelogs/unreleased/api-branch-status.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Expose merge status for branch API' +merge_request: +author: Robert Schilling diff --git a/doc/api/branches.md b/doc/api/branches.md index 07dfa5d4d7f..ffcfea41453 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -22,6 +22,7 @@ Example response: [ { "name": "master", + "merged": false, "protected": true, "developers_can_push": false, "developers_can_merge": false, @@ -65,6 +66,7 @@ Example response: ```json { "name": "master", + "merged": false, "protected": true, "developers_can_push": false, "developers_can_merge": false, @@ -123,6 +125,7 @@ Example response: ] }, "name": "master", + "merged": false, "protected": true, "developers_can_push": true, "developers_can_merge": true @@ -166,6 +169,7 @@ Example response: ] }, "name": "master", + "merged": false, "protected": false, "developers_can_push": false, "developers_can_merge": false @@ -206,6 +210,7 @@ Example response: ] }, "name": "newbranch", + "merged": false, "protected": false, "developers_can_push": false, "developers_can_merge": false diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fdb19558c1c..d5dfb8d00be 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -141,8 +141,12 @@ module API options[:project].repository.commit(repo_branch.dereferenced_target) end + expose :merged do |repo_branch, options| + options[:project].repository.merged_to_root_ref?(repo_branch.name) + end + expose :protected do |repo_branch, options| - options[:project].protected_branch? repo_branch.name + options[:project].protected_branch?(repo_branch.name) end expose :developers_can_push do |repo_branch, options| diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index fe6b875b997..28fbae18de1 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -31,11 +31,22 @@ describe API::API, api: true do expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) + expect(json_response['merged']).to eq(false) expect(json_response['protected']).to eq(false) expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_merge']).to eq(false) end + context 'on a merged branch' do + it "returns the branch information for a single branch" do + get api("/projects/#{project.id}/repository/branches/merge-test", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('merge-test') + expect(json_response['merged']).to eq(true) + end + end + it "returns a 403 error if guest" do get api("/projects/#{project.id}/repository/branches", user2) expect(response).to have_http_status(403) -- cgit v1.2.1 From edc97c9dc6c3fa43778068f2067cb5911e7ee3f0 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 29 Nov 2016 17:40:16 +0000 Subject: Use created date from last_deployment Adds test Adds changelog entry --- .../environments/components/environment_item.js.es6 | 10 ++++++++-- changelogs/unreleased/24844-environments-date.yml | 4 ++++ spec/javascripts/environments/environment_item_spec.js.es6 | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/24844-environments-date.yml diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 07f49cce3dc..a3ba3c762d7 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -147,6 +147,12 @@ this.model.last_deployment.deployable; }, + canShowDate() { + return this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable.created_at; + }, + /** * Human readable date. * @@ -155,7 +161,7 @@ createdDate() { const timeagoInstance = new timeago(); // eslint-disable-line - return timeagoInstance.format(this.model.created_at); + return timeagoInstance.format(this.model.last_deployment.deployable.created_at); }, /** @@ -453,7 +459,7 @@ {{createdDate}} diff --git a/changelogs/unreleased/24844-environments-date.yml b/changelogs/unreleased/24844-environments-date.yml new file mode 100644 index 00000000000..2bc23d40a68 --- /dev/null +++ b/changelogs/unreleased/24844-environments-date.yml @@ -0,0 +1,4 @@ +--- +title: Fixes Environments displaying incorrect date since 8.14 upgrade +merge_request: +author: diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index 14e90a9dd1b..3e2fa730c8d 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -1,4 +1,5 @@ //= require vue +//= require timeago //= require environments/components/environment_item describe('Environment item', () => { @@ -109,6 +110,8 @@ describe('Environment item', () => { name: 'deploy', build_path: '/root/ci-folders/builds/1279', retry_path: '/root/ci-folders/builds/1279/retry', + created_at: '2016-11-29T18:11:58.430Z', + updated_at: '2016-11-29T18:11:58.430Z', }, manual_actions: [ { @@ -149,6 +152,17 @@ describe('Environment item', () => { ).toContain('#'); }); + it('should render last deployment date', () => { + const timeagoInstance = new timeago(); // eslint-disable-line + const formatedDate = timeagoInstance.format( + environment.last_deployment.deployable.created_at + ); + + expect( + component.$el.querySelector('.environment-created-date-timeago').textContent + ).toContain(formatedDate); + }); + describe('With user information', () => { it('should render user avatar with link to profile', () => { expect( -- cgit v1.2.1 From 54e628740417a2f7fc0f8d77db095ff31798a335 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 29 Nov 2016 18:54:34 +0000 Subject: Improvements after review --- .../environments/components/environment_item.js.es6 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index a3ba3c762d7..c842b9e418f 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -23,6 +23,7 @@ window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; + window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line gl.environmentsList.EnvironmentItem = Vue.component('environment-item', { @@ -147,10 +148,15 @@ this.model.last_deployment.deployable; }, + /** + * Verifies if the date to be shown is present. + * + * @returns {Boolean|Undefined} + */ canShowDate() { return this.model.last_deployment && this.model.last_deployment.deployable && - this.model.last_deployment.deployable.created_at; + this.model.last_deployment.deployable !== undefined; }, /** @@ -159,9 +165,9 @@ * @returns {String} */ createdDate() { - const timeagoInstance = new timeago(); // eslint-disable-line - - return timeagoInstance.format(this.model.last_deployment.deployable.created_at); + return window.gl.environmentsList.timeagoInstance.format( + this.model.last_deployment.deployable.created_at + ); }, /** -- cgit v1.2.1 From 4681ab1df1c3d05af58335dd065ae1834aab5d35 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 30 Nov 2016 14:08:51 +0100 Subject: Fix Rubocop offense in merge request specs --- spec/requests/api/merge_requests_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 1e25ae60eef..dce754fa1cb 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -465,7 +465,6 @@ describe API::API, api: true do expect(response).to have_http_status(200) end - it "enables merge when pipeline succeeds if the pipeline is active" do allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) -- cgit v1.2.1 From cd5813ee21c3433fecbbe89185976843a77ad04d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 30 Nov 2016 14:24:12 +0000 Subject: Fix comma-dangle in function's arguments errors --- .../javascripts/environments/components/environment_item.js.es6 | 2 +- spec/javascripts/environments/environment_item_spec.js.es6 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index c842b9e418f..7ead8a18c2a 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -166,7 +166,7 @@ */ createdDate() { return window.gl.environmentsList.timeagoInstance.format( - this.model.last_deployment.deployable.created_at + this.model.last_deployment.deployable.created_at, ); }, diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index 3e2fa730c8d..5d7c6b2411d 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -155,11 +155,11 @@ describe('Environment item', () => { it('should render last deployment date', () => { const timeagoInstance = new timeago(); // eslint-disable-line const formatedDate = timeagoInstance.format( - environment.last_deployment.deployable.created_at + environment.last_deployment.deployable.created_at, ); expect( - component.$el.querySelector('.environment-created-date-timeago').textContent + component.$el.querySelector('.environment-created-date-timeago').textContent, ).toContain(formatedDate); }); -- cgit v1.2.1 From c347170853926a4ce0402fe1b647f578b3da620e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 30 Nov 2016 07:01:45 -0800 Subject: Revert bump in rufus-scheduler Somehow `bundle install` with an update to Sidekiq-cron caused rufus-scheduler to be bumped, when it doesn't appear absolutely necessary. Closes #25160 --- Gemfile.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1db0e466164..5a14ed6fede 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -614,8 +614,7 @@ GEM rubyntlm (0.5.2) rubypants (0.2.0) rubyzip (1.2.0) - rufus-scheduler (3.3.0) - tzinfo + rufus-scheduler (3.1.10) rugged (0.24.0) safe_yaml (1.0.4) sanitize (2.1.0) -- cgit v1.2.1 From a59e75a17f52d2c71501b0f61686b3a546501d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 30 Nov 2016 16:15:02 +0100 Subject: Make the downtime_check task happy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- db/migrate/20130319214458_create_forked_project_links.rb | 2 ++ db/migrate/20130506090604_create_deploy_keys_projects.rb | 2 ++ db/migrate/20130617095603_create_users_groups.rb | 2 ++ db/migrate/20130711063759_create_project_group_links.rb | 2 ++ db/migrate/20131112114325_create_broadcast_messages.rb | 2 ++ db/migrate/20140122112253_create_merge_request_diffs.rb | 2 ++ db/migrate/20140209025651_create_emails.rb | 4 +++- db/migrate/20140625115202_create_users_star_projects.rb | 2 ++ db/migrate/20140729134820_create_labels.rb | 2 ++ db/migrate/20140729140420_create_label_links.rb | 2 ++ db/migrate/20140914113604_add_members_table.rb | 2 ++ db/migrate/20140914173417_remove_old_member_tables.rb | 2 ++ db/migrate/20141118150935_add_audit_event.rb | 2 ++ db/migrate/20141216155758_create_doorkeeper_tables.rb | 2 ++ db/migrate/20150108073740_create_application_settings.rb | 2 ++ db/migrate/20150313012111_create_subscriptions_table.rb | 6 ++++-- db/migrate/20150806104937_create_abuse_reports.rb | 2 ++ db/migrate/20151103134857_create_lfs_objects.rb | 2 ++ db/migrate/20151103134958_create_lfs_objects_projects.rb | 2 ++ db/migrate/20151105094515_create_releases.rb | 2 ++ db/migrate/20160212123307_create_tasks.rb | 2 ++ db/migrate/20160416180807_add_award_emoji.rb | 2 ++ 22 files changed, 47 insertions(+), 3 deletions(-) diff --git a/db/migrate/20130319214458_create_forked_project_links.rb b/db/migrate/20130319214458_create_forked_project_links.rb index 41b0b700a6f..065a5e08243 100644 --- a/db/migrate/20130319214458_create_forked_project_links.rb +++ b/db/migrate/20130319214458_create_forked_project_links.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateForkedProjectLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :forked_project_links do |t| t.integer :forked_to_project_id, null: false diff --git a/db/migrate/20130506090604_create_deploy_keys_projects.rb b/db/migrate/20130506090604_create_deploy_keys_projects.rb index f2e416d3b6f..8b9662a27c3 100644 --- a/db/migrate/20130506090604_create_deploy_keys_projects.rb +++ b/db/migrate/20130506090604_create_deploy_keys_projects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateDeployKeysProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :deploy_keys_projects do |t| t.integer :deploy_key_id, null: false diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb index cb098aa9bf9..4ba7d0c9461 100644 --- a/db/migrate/20130617095603_create_users_groups.rb +++ b/db/migrate/20130617095603_create_users_groups.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateUsersGroups < ActiveRecord::Migration + DOWNTIME = false + def change create_table :users_groups do |t| t.integer :group_access, null: false diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb index 8da7ff6f4cd..efccb2aa938 100644 --- a/db/migrate/20130711063759_create_project_group_links.rb +++ b/db/migrate/20130711063759_create_project_group_links.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateProjectGroupLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :project_group_links do |t| t.integer :project_id, null: false diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb index 4ada40f1b66..ad2549e53af 100644 --- a/db/migrate/20131112114325_create_broadcast_messages.rb +++ b/db/migrate/20131112114325_create_broadcast_messages.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateBroadcastMessages < ActiveRecord::Migration + DOWNTIME = false + def change create_table :broadcast_messages do |t| t.text :message, null: false diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb index 68448c91529..6c7a92b6950 100644 --- a/db/migrate/20140122112253_create_merge_request_diffs.rb +++ b/db/migrate/20140122112253_create_merge_request_diffs.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateMergeRequestDiffs < ActiveRecord::Migration + DOWNTIME = false + def up create_table :merge_request_diffs do |t| t.string :state, null: false, default: 'collected' diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb index 48d14682628..51886f8fc89 100644 --- a/db/migrate/20140209025651_create_emails.rb +++ b/db/migrate/20140209025651_create_emails.rb @@ -1,10 +1,12 @@ # rubocop:disable all class CreateEmails < ActiveRecord::Migration + DOWNTIME = false + def change create_table :emails do |t| t.integer :user_id, null: false t.string :email, null: false - + t.timestamps null: true end diff --git a/db/migrate/20140625115202_create_users_star_projects.rb b/db/migrate/20140625115202_create_users_star_projects.rb index c50bc4bd614..d4f3fe5ac62 100644 --- a/db/migrate/20140625115202_create_users_star_projects.rb +++ b/db/migrate/20140625115202_create_users_star_projects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateUsersStarProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :users_star_projects do |t| t.integer :project_id, null: false diff --git a/db/migrate/20140729134820_create_labels.rb b/db/migrate/20140729134820_create_labels.rb index 589aced0d76..66d20e741a6 100644 --- a/db/migrate/20140729134820_create_labels.rb +++ b/db/migrate/20140729134820_create_labels.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLabels < ActiveRecord::Migration + DOWNTIME = false + def change create_table :labels do |t| t.string :title diff --git a/db/migrate/20140729140420_create_label_links.rb b/db/migrate/20140729140420_create_label_links.rb index abdbaa1bd1a..dacd9f2e4b6 100644 --- a/db/migrate/20140729140420_create_label_links.rb +++ b/db/migrate/20140729140420_create_label_links.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLabelLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :label_links do |t| t.integer :label_id diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb index fb0876dd520..0f76bb0ef79 100644 --- a/db/migrate/20140914113604_add_members_table.rb +++ b/db/migrate/20140914113604_add_members_table.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddMembersTable < ActiveRecord::Migration + DOWNTIME = false + def change create_table :members do |t| t.integer :access_level, null: false diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb index 067dc21ccf1..d2ab326ef1f 100644 --- a/db/migrate/20140914173417_remove_old_member_tables.rb +++ b/db/migrate/20140914173417_remove_old_member_tables.rb @@ -1,5 +1,7 @@ # rubocop:disable all class RemoveOldMemberTables < ActiveRecord::Migration + DOWNTIME = false + def up drop_table :users_groups drop_table :users_projects diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb index f0452f46459..52d70b4a0ac 100644 --- a/db/migrate/20141118150935_add_audit_event.rb +++ b/db/migrate/20141118150935_add_audit_event.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddAuditEvent < ActiveRecord::Migration + DOWNTIME = false + def change create_table :audit_events do |t| t.integer :author_id, null: false diff --git a/db/migrate/20141216155758_create_doorkeeper_tables.rb b/db/migrate/20141216155758_create_doorkeeper_tables.rb index 1c4d32e133c..17e45a77291 100644 --- a/db/migrate/20141216155758_create_doorkeeper_tables.rb +++ b/db/migrate/20141216155758_create_doorkeeper_tables.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateDoorkeeperTables < ActiveRecord::Migration + DOWNTIME = false + def change create_table :oauth_applications do |t| t.string :name, null: false diff --git a/db/migrate/20150108073740_create_application_settings.rb b/db/migrate/20150108073740_create_application_settings.rb index c26a7c39574..0e4c66ca8c0 100644 --- a/db/migrate/20150108073740_create_application_settings.rb +++ b/db/migrate/20150108073740_create_application_settings.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateApplicationSettings < ActiveRecord::Migration + DOWNTIME = false + def change create_table :application_settings do |t| t.integer :default_projects_limit diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb index 0977c9adfec..a9a8435330d 100644 --- a/db/migrate/20150313012111_create_subscriptions_table.rb +++ b/db/migrate/20150313012111_create_subscriptions_table.rb @@ -1,15 +1,17 @@ # rubocop:disable all class CreateSubscriptionsTable < ActiveRecord::Migration + DOWNTIME = false + def change create_table :subscriptions do |t| t.integer :user_id t.references :subscribable, polymorphic: true t.boolean :subscribed - + t.timestamps null: true end - add_index :subscriptions, + add_index :subscriptions, [:subscribable_id, :subscribable_type, :user_id], unique: true, name: 'subscriptions_user_id_and_ref_fields' diff --git a/db/migrate/20150806104937_create_abuse_reports.rb b/db/migrate/20150806104937_create_abuse_reports.rb index 9f1512db862..52aed9e1d1d 100644 --- a/db/migrate/20150806104937_create_abuse_reports.rb +++ b/db/migrate/20150806104937_create_abuse_reports.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateAbuseReports < ActiveRecord::Migration + DOWNTIME = false + def change create_table :abuse_reports do |t| t.integer :reporter_id diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb index fadaf637cec..db6fa27199b 100644 --- a/db/migrate/20151103134857_create_lfs_objects.rb +++ b/db/migrate/20151103134857_create_lfs_objects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLfsObjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :lfs_objects do |t| t.string :oid, null: false, unique: true diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb index e830cbe4656..5af1c39fd9c 100644 --- a/db/migrate/20151103134958_create_lfs_objects_projects.rb +++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLfsObjectsProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :lfs_objects_projects do |t| t.integer :lfs_object_id, null: false diff --git a/db/migrate/20151105094515_create_releases.rb b/db/migrate/20151105094515_create_releases.rb index 87f692c64d0..34dd7a10942 100644 --- a/db/migrate/20151105094515_create_releases.rb +++ b/db/migrate/20151105094515_create_releases.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateReleases < ActiveRecord::Migration + DOWNTIME = false + def change create_table :releases do |t| t.string :tag diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb index 8b5b1dd694d..cd3ad0e4cd8 100644 --- a/db/migrate/20160212123307_create_tasks.rb +++ b/db/migrate/20160212123307_create_tasks.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateTasks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :tasks do |t| t.references :user, null: false, index: true diff --git a/db/migrate/20160416180807_add_award_emoji.rb b/db/migrate/20160416180807_add_award_emoji.rb index c0957f028a8..0d252e5044e 100644 --- a/db/migrate/20160416180807_add_award_emoji.rb +++ b/db/migrate/20160416180807_add_award_emoji.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddAwardEmoji < ActiveRecord::Migration + DOWNTIME = false + def change create_table :award_emoji do |t| t.string :name -- cgit v1.2.1 From c7b7a6b33f20699f293ddeec35ea9f767341e462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 30 Nov 2016 16:45:09 +0100 Subject: Disable the ee_compat_check task on dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We usually use it for security fixes that don't need to be ported to EE and the task seems to hang when run against the security branch. Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c357c489f8..c7528029c89 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -229,7 +229,6 @@ rake ee_compat_check: <<: *exec only: - branches@gitlab-org/gitlab-ce - - branches@gitlab/gitlabhq except: - master - tags -- cgit v1.2.1 From 43e5009a301d474225bf39e0efc5766b4b6be0c1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 23 Nov 2016 14:44:05 +0000 Subject: Pipelines tabs --- .../lib/utils/bootstrap_linked_tabs.js.es6 | 110 +++++++++++++++++++++ app/controllers/projects/pipelines_controller.rb | 4 + app/views/projects/pipelines/_with_tabs.html.haml | 23 ++++- config/routes/project.rb | 1 + spec/features/projects/pipelines_spec.rb | 7 ++ 5 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 new file mode 100644 index 00000000000..28239cd66a9 --- /dev/null +++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 @@ -0,0 +1,110 @@ + +/** + * Linked Tabs + * + * Handles persisting and restores the current tab selection and content. + * Reusable component for static content. + * + * ### Example Markup + * + *