From 8bbaf266cc8d1ff03c4f7a4173ce9a7ac60c0cbb Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 8 Mar 2018 17:33:06 +0000 Subject: [PATCH 001/146] Handle empty states for job page --- .../projects/jobs/_empty_state.html.haml | 5 ++-- .../projects/jobs/_empty_status.html.haml | 29 +++++++++++++++++++ app/views/projects/jobs/show.html.haml | 23 +++------------ .../unreleased/42568-pipeline-empty-state.yml | 5 ++++ db/fixtures/development/14_pipelines.rb | 4 +++ spec/features/projects/jobs_spec.rb | 27 +++++++++++++++++ 6 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 app/views/projects/jobs/_empty_status.html.haml create mode 100644 changelogs/unreleased/42568-pipeline-empty-state.yml diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml index c66313bdbf3..311934d9c33 100644 --- a/app/views/projects/jobs/_empty_state.html.haml +++ b/app/views/projects/jobs/_empty_state.html.haml @@ -1,7 +1,7 @@ - illustration = local_assigns.fetch(:illustration) - illustration_size = local_assigns.fetch(:illustration_size) - title = local_assigns.fetch(:title) -- content = local_assigns.fetch(:content) +- content = local_assigns.fetch(:content, nil) - action = local_assigns.fetch(:action, nil) .row.empty-state @@ -11,7 +11,8 @@ .col-xs-12 .text-content %h4.text-center= title - %p= content + - if content + %p= content - if action .text-center = action diff --git a/app/views/projects/jobs/_empty_status.html.haml b/app/views/projects/jobs/_empty_status.html.haml new file mode 100644 index 00000000000..707085cddd5 --- /dev/null +++ b/app/views/projects/jobs/_empty_status.html.haml @@ -0,0 +1,29 @@ +- if @build.playable? + = render 'empty_state', + illustration: 'illustrations/manual_action.svg', + illustration_size: 'svg-394', + title: _('This job requires a manual action'), + content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), + action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), method: :post, class: 'btn btn-primary', title: _('Trigger this manual action') ) +- elsif @build.created? + = render 'empty_state', + illustration: 'illustrations/job_not_triggered.svg', + illustration_size: 'svg-306', + title: _('This job has not been triggered yet'), + content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered') +- elsif @build.canceled? + = render 'empty_state', + illustration: 'illustrations/canceled-job_empty.svg', + illustration_size: 'svg-430', + title: _('This job has been canceled') +- elsif @build.skipped? + = render 'empty_state', + illustration: 'illustrations/canceled-job_empty.svg', + illustration_size: 'svg-430', + title: _('This job has been skipped') +- else + = render 'empty_state', + illustration: 'illustrations/pending_job_empty.svg', + illustration_size: 'svg-430', + title: _('This job has not started yet'), + content: _('This job is in pending state and is waiting to be picked by a runner') diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 849c273db8c..04c28841511 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -54,7 +54,8 @@ Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)} - else Job has been erased #{time_ago_with_tooltip(@build.erased_at)} - - if @build.started? + + - if @build.has_trace? .build-trace-container.prepend-top-default .top-bar.js-top-bar .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< @@ -88,25 +89,9 @@ %pre.build-trace#build-trace %code.bash.js-build-output .build-loader-animation.js-build-refresh - - elsif @build.playable? - = render 'empty_state', - illustration: 'illustrations/manual_action.svg', - illustration_size: 'svg-394', - title: _('This job requires a manual action'), - content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), - action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), method: :post, class: 'btn btn-primary', title: _('Trigger this manual action') ) - - elsif @build.created? - = render 'empty_state', - illustration: 'illustrations/job_not_triggered.svg', - illustration_size: 'svg-306', - title: _('This job has not been triggered yet'), - content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered') - else - = render 'empty_state', - illustration: 'illustrations/pending_job_empty.svg', - illustration_size: 'svg-430', - title: _('This job has not started yet'), - content: _('This job is in pending state and is waiting to be picked by a runner') + = render "empty_status" + = render "sidebar" .js-build-options{ data: javascript_build_options } diff --git a/changelogs/unreleased/42568-pipeline-empty-state.yml b/changelogs/unreleased/42568-pipeline-empty-state.yml new file mode 100644 index 00000000000..d36edcf1b37 --- /dev/null +++ b/changelogs/unreleased/42568-pipeline-empty-state.yml @@ -0,0 +1,5 @@ +--- +title: Improve empty state for canceled job +merge_request: 17646 +author: +type: fixed diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index d3a63aa2a78..fe21b7aad0e 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -30,6 +30,10 @@ class Gitlab::Seeder::Pipelines queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true, queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'spinach:osx1', stage: 'test', status: :canceled, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'spinach:osx1', stage: 'test', status: :running, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, # deploy stage { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 5d311f2dde3..aa4c2757a25 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -419,6 +419,33 @@ feature 'Jobs' do expect(page).to have_content('This job is in pending state and is waiting to be picked by a runner') end end + + context 'Canceled job' do + context 'with log' do + let(:job) { create(:ci_build, :canceled, :trace_artifact, pipeline: pipeline) } + + before do + visit project_job_path(project, job) + end + + it 'renders job log' do + expect(page).to have_selector('.js-build-output') + end + end + + context 'without log' do + let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } + + before do + visit project_job_path(project, job) + end + + it 'renders empty state' do + expect(page).not_to have_selector('.js-build-output') + expect(page).to have_content('This job has been canceled') + end + end + end end describe "POST /:project/jobs/:id/cancel", :js do From cd6a58c8100356be95019be6a69724066469bd42 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 9 Mar 2018 17:59:59 +0000 Subject: [PATCH 002/146] Fix broken test --- features/steps/shared/builds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb index a3e4459f169..f5950145348 100644 --- a/features/steps/shared/builds.rb +++ b/features/steps/shared/builds.rb @@ -11,7 +11,7 @@ module SharedBuilds step 'project has a recent build' do @pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master') - @build = create(:ci_build, :running, :coverage, pipeline: @pipeline) + @build = create(:ci_build, :running, :coverage, :trace_artifact, pipeline: @pipeline) end step 'recent build is successful' do From c7776a1d32621740f1eea17ec0385889280df65e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 12 Mar 2018 19:13:13 +0000 Subject: [PATCH 003/146] Render skipped illustration for skipped state --- app/views/projects/jobs/_empty_status.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/jobs/_empty_status.html.haml b/app/views/projects/jobs/_empty_status.html.haml index 707085cddd5..9e144dbfe32 100644 --- a/app/views/projects/jobs/_empty_status.html.haml +++ b/app/views/projects/jobs/_empty_status.html.haml @@ -18,7 +18,7 @@ title: _('This job has been canceled') - elsif @build.skipped? = render 'empty_state', - illustration: 'illustrations/canceled-job_empty.svg', + illustration: 'illustrations/skipped-job_empty.svg', illustration_size: 'svg-430', title: _('This job has been skipped') - else From 18bce8fcfeaaeeda5aea37f21adedcdc49dd4147 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 19 Mar 2018 10:07:30 +0000 Subject: [PATCH 004/146] Update partial name and remove changes to database fixtures --- .../jobs/{_empty_status.html.haml => _empty_states.html.haml} | 0 app/views/projects/jobs/show.html.haml | 2 +- db/fixtures/development/14_pipelines.rb | 4 ---- 3 files changed, 1 insertion(+), 5 deletions(-) rename app/views/projects/jobs/{_empty_status.html.haml => _empty_states.html.haml} (100%) diff --git a/app/views/projects/jobs/_empty_status.html.haml b/app/views/projects/jobs/_empty_states.html.haml similarity index 100% rename from app/views/projects/jobs/_empty_status.html.haml rename to app/views/projects/jobs/_empty_states.html.haml diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 04c28841511..1db01133900 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -90,7 +90,7 @@ %code.bash.js-build-output .build-loader-animation.js-build-refresh - else - = render "empty_status" + = render "empty_states" = render "sidebar" diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index fe21b7aad0e..d3a63aa2a78 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -30,10 +30,6 @@ class Gitlab::Seeder::Pipelines queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true, queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, - { name: 'spinach:osx1', stage: 'test', status: :canceled, - queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, - { name: 'spinach:osx1', stage: 'test', status: :running, - queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, # deploy stage { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, From d696194f8f80dd6fe4feb9ec7bb42a29ab9e98f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 24 Mar 2018 11:46:04 +0100 Subject: [PATCH 005/146] Add illustrations to build statuses --- lib/gitlab/ci/status/canceled.rb | 4 ++++ lib/gitlab/ci/status/core.rb | 4 ++++ lib/gitlab/ci/status/created.rb | 4 ++++ lib/gitlab/ci/status/manual.rb | 4 ++++ lib/gitlab/ci/status/pending.rb | 4 ++++ lib/gitlab/ci/status/skipped.rb | 4 ++++ spec/lib/gitlab/ci/status/build/factory_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/canceled_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/created_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/manual_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/pending_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/skipped_spec.rb | 4 ++++ 12 files changed, 48 insertions(+) diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb index e6195a60d4f..9a25375678d 100644 --- a/lib/gitlab/ci/status/canceled.rb +++ b/lib/gitlab/ci/status/canceled.rb @@ -17,6 +17,10 @@ module Gitlab def favicon 'favicon_status_canceled' end + + def illustration + 'canceled-job_empty' + end end end end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index d4fd83b93f8..89fb9a0b7bc 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -22,6 +22,10 @@ module Gitlab raise NotImplementedError end + def illustration + raise NotImplementedError + end + def label raise NotImplementedError end diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb index 846f00b83dd..033ecbe9380 100644 --- a/lib/gitlab/ci/status/created.rb +++ b/lib/gitlab/ci/status/created.rb @@ -17,6 +17,10 @@ module Gitlab def favicon 'favicon_status_created' end + + def illustration + 'job_not_triggered' + end end end end diff --git a/lib/gitlab/ci/status/manual.rb b/lib/gitlab/ci/status/manual.rb index fc387e2fd25..92c449e2c63 100644 --- a/lib/gitlab/ci/status/manual.rb +++ b/lib/gitlab/ci/status/manual.rb @@ -17,6 +17,10 @@ module Gitlab def favicon 'favicon_status_manual' end + + def illustration + 'manual_action' + end end end end diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb index 6780780db32..63c0b5b501f 100644 --- a/lib/gitlab/ci/status/pending.rb +++ b/lib/gitlab/ci/status/pending.rb @@ -17,6 +17,10 @@ module Gitlab def favicon 'favicon_status_pending' end + + def illustration + 'pending_job_empty' + end end end end diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb index 0dbdc4de426..5dbe2847344 100644 --- a/lib/gitlab/ci/status/skipped.rb +++ b/lib/gitlab/ci/status/skipped.rb @@ -17,6 +17,10 @@ module Gitlab def favicon 'favicon_status_skipped' end + + def illustration + 'skipped-job_empty' + end end end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index d196bc6a4c2..04b718c5897 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -115,6 +115,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'canceled' expect(status.icon).to eq 'status_canceled' expect(status.favicon).to eq 'favicon_status_canceled' + expect(status.illustration).to eq 'canceled-job_empty' expect(status.label).to eq 'canceled' expect(status).to have_details expect(status).to have_action @@ -167,6 +168,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'pending' expect(status.icon).to eq 'status_pending' expect(status.favicon).to eq 'favicon_status_pending' + expect(status.illustration).to eq 'pending_job_empty' expect(status.label).to eq 'pending' expect(status).to have_details expect(status).to have_action @@ -192,6 +194,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'skipped' expect(status.icon).to eq 'status_skipped' expect(status.favicon).to eq 'favicon_status_skipped' + expect(status.illustration).to eq 'skipped-job_empty' expect(status.label).to eq 'skipped' expect(status).to have_details expect(status).not_to have_action @@ -221,6 +224,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.group).to eq 'manual' expect(status.icon).to eq 'status_manual' expect(status.favicon).to eq 'favicon_status_manual' + expect(status.illustration).to eq 'manual_action' expect(status.label).to include 'manual play action' expect(status).to have_details expect(status.action_path).to include 'play' diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index dc74d7e28c5..fc1d9e726ab 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -21,6 +21,10 @@ describe Gitlab::Ci::Status::Canceled do it { expect(subject.favicon).to eq 'favicon_status_canceled' } end + describe '#illustration' do + it { expect(subject.illustration).to eq 'canceled-job_empty' } + end + describe '#group' do it { expect(subject.group).to eq 'canceled' } end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index ce4333f2aca..f7e3b0d8f1a 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -21,6 +21,10 @@ describe Gitlab::Ci::Status::Created do it { expect(subject.favicon).to eq 'favicon_status_created' } end + describe '#illustration' do + it { expect(subject.illustration).to eq 'job_not_triggered' } + end + describe '#group' do it { expect(subject.group).to eq 'created' } end diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb index 0463f2e1aff..161c4774ff3 100644 --- a/spec/lib/gitlab/ci/status/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/manual_spec.rb @@ -21,6 +21,10 @@ describe Gitlab::Ci::Status::Manual do it { expect(subject.favicon).to eq 'favicon_status_manual' } end + describe '#illustration' do + it { expect(subject.illustration).to eq 'manual_action' } + end + describe '#group' do it { expect(subject.group).to eq 'manual' } end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index 0e25358dd8a..92349308842 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -21,6 +21,10 @@ describe Gitlab::Ci::Status::Pending do it { expect(subject.favicon).to eq 'favicon_status_pending' } end + describe '#illustration' do + it { expect(subject.illustration).to eq 'pending_job_empty' } + end + describe '#group' do it { expect(subject.group).to eq 'pending' } end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index 63694ca0ea6..993490c6d4e 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -21,6 +21,10 @@ describe Gitlab::Ci::Status::Skipped do it { expect(subject.favicon).to eq 'favicon_status_skipped' } end + describe '#illustration' do + it { expect(subject.illustration).to eq 'skipped-job_empty' } + end + describe '#group' do it { expect(subject.group).to eq 'skipped' } end From ec5dae98cf78c458e8d4f7ca71700a02715cb8c3 Mon Sep 17 00:00:00 2001 From: Fabian Schneider Date: Sun, 25 Mar 2018 15:50:09 +0200 Subject: [PATCH 006/146] Fix personal access token clipboard button style --- app/assets/stylesheets/pages/profile.scss | 7 +------ app/views/profiles/personal_access_tokens/index.html.haml | 6 ++++-- .../43976-fix-access-token-clipboard-button-style.yml | 5 +++++ 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/43976-fix-access-token-clipboard-button-style.yml diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index ac745019319..b199f9876d3 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -210,13 +210,8 @@ } .created-personal-access-token-container { - #created-personal-access-token { - width: 90%; - display: inline; - } - .btn-clipboard { - margin-left: 5px; + border: 1px solid $border-color; } } diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index 78848542810..b96251cd982 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -19,8 +19,10 @@ %h5.prepend-top-0 Your New Personal Access Token .form-group - = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block" - = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left") + .input-group + = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block" + %span.input-group-btn + = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left", class: "btn-default btn-clipboard") %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. %hr diff --git a/changelogs/unreleased/43976-fix-access-token-clipboard-button-style.yml b/changelogs/unreleased/43976-fix-access-token-clipboard-button-style.yml new file mode 100644 index 00000000000..b341d5dfa7f --- /dev/null +++ b/changelogs/unreleased/43976-fix-access-token-clipboard-button-style.yml @@ -0,0 +1,5 @@ +--- +title: Fix personal access token clipboard button style +merge_request: 17978 +author: Fabian Schneider +type: fixed From d0349362a7681dc72636b0041a28d58a6e979707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 26 Mar 2018 17:28:53 +0200 Subject: [PATCH 007/146] Extend Gitlab::Ci::Status#illustration to include metadata --- lib/gitlab/ci/status/canceled.rb | 6 +++++- lib/gitlab/ci/status/created.rb | 7 ++++++- lib/gitlab/ci/status/manual.rb | 7 ++++++- lib/gitlab/ci/status/pending.rb | 7 ++++++- lib/gitlab/ci/status/skipped.rb | 6 +++++- spec/lib/gitlab/ci/status/build/factory_spec.rb | 8 ++++---- spec/lib/gitlab/ci/status/canceled_spec.rb | 2 +- spec/lib/gitlab/ci/status/created_spec.rb | 2 +- spec/lib/gitlab/ci/status/manual_spec.rb | 2 +- spec/lib/gitlab/ci/status/pending_spec.rb | 2 +- spec/lib/gitlab/ci/status/skipped_spec.rb | 2 +- 11 files changed, 37 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb index 9a25375678d..bea8dcd5587 100644 --- a/lib/gitlab/ci/status/canceled.rb +++ b/lib/gitlab/ci/status/canceled.rb @@ -19,7 +19,11 @@ module Gitlab end def illustration - 'canceled-job_empty' + { + image: 'illustrations/canceled-job_empty.svg', + size: 'svg-430', + title: _('This job has been canceled') + } end end end diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb index 033ecbe9380..3b2a9e5d0ef 100644 --- a/lib/gitlab/ci/status/created.rb +++ b/lib/gitlab/ci/status/created.rb @@ -19,7 +19,12 @@ module Gitlab end def illustration - 'job_not_triggered' + { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: _('This job has not been triggered yet'), + content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered') + } end end end diff --git a/lib/gitlab/ci/status/manual.rb b/lib/gitlab/ci/status/manual.rb index 92c449e2c63..cb587a7815e 100644 --- a/lib/gitlab/ci/status/manual.rb +++ b/lib/gitlab/ci/status/manual.rb @@ -19,7 +19,12 @@ module Gitlab end def illustration - 'manual_action' + { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: _('This job requires a manual action'), + content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments') + } end end end diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb index 63c0b5b501f..6a27a83696a 100644 --- a/lib/gitlab/ci/status/pending.rb +++ b/lib/gitlab/ci/status/pending.rb @@ -19,7 +19,12 @@ module Gitlab end def illustration - 'pending_job_empty' + { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: _('This job has not started yet'), + content: _('This job is in pending state and is waiting to be picked by a runner') + } end end end diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb index 5dbe2847344..ff7529b5f03 100644 --- a/lib/gitlab/ci/status/skipped.rb +++ b/lib/gitlab/ci/status/skipped.rb @@ -19,7 +19,11 @@ module Gitlab end def illustration - 'skipped-job_empty' + { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: _('This job has been skipped') + } end end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 04b718c5897..0c9a751561f 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -115,7 +115,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'canceled' expect(status.icon).to eq 'status_canceled' expect(status.favicon).to eq 'favicon_status_canceled' - expect(status.illustration).to eq 'canceled-job_empty' + expect(status.illustration).to include(:image, :size, :title) expect(status.label).to eq 'canceled' expect(status).to have_details expect(status).to have_action @@ -168,7 +168,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'pending' expect(status.icon).to eq 'status_pending' expect(status.favicon).to eq 'favicon_status_pending' - expect(status.illustration).to eq 'pending_job_empty' + expect(status.illustration).to include(:image, :size, :title, :content) expect(status.label).to eq 'pending' expect(status).to have_details expect(status).to have_action @@ -194,7 +194,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.text).to eq 'skipped' expect(status.icon).to eq 'status_skipped' expect(status.favicon).to eq 'favicon_status_skipped' - expect(status.illustration).to eq 'skipped-job_empty' + expect(status.illustration).to include(:image, :size, :title) expect(status.label).to eq 'skipped' expect(status).to have_details expect(status).not_to have_action @@ -224,7 +224,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.group).to eq 'manual' expect(status.icon).to eq 'status_manual' expect(status.favicon).to eq 'favicon_status_manual' - expect(status.illustration).to eq 'manual_action' + expect(status.illustration).to include(:image, :size, :title, :content) expect(status.label).to include 'manual play action' expect(status).to have_details expect(status.action_path).to include 'play' diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index fc1d9e726ab..70cf2c1d300 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Canceled do end describe '#illustration' do - it { expect(subject.illustration).to eq 'canceled-job_empty' } + it { expect(subject.illustration).to include(:image, :size, :title) } end describe '#group' do diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index f7e3b0d8f1a..8a7771e0dc8 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Created do end describe '#illustration' do - it { expect(subject.illustration).to eq 'job_not_triggered' } + it { expect(subject.illustration).to include(:image, :size, :title, :content) } end describe '#group' do diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb index 161c4774ff3..9b2dffe53d0 100644 --- a/spec/lib/gitlab/ci/status/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/manual_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Manual do end describe '#illustration' do - it { expect(subject.illustration).to eq 'manual_action' } + it { expect(subject.illustration).to include(:image, :size, :title, :content) } end describe '#group' do diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index 92349308842..d859371df9d 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Pending do end describe '#illustration' do - it { expect(subject.illustration).to eq 'pending_job_empty' } + it { expect(subject.illustration).to include(:image, :size, :title, :content) } end describe '#group' do diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index 993490c6d4e..f7fa8a035f2 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Skipped do end describe '#illustration' do - it { expect(subject.illustration).to eq 'skipped-job_empty' } + it { expect(subject.illustration).to include(:image, :size, :title) } end describe '#group' do From 4b0cbf630629cf1db5285baa0880513e4dd5ca16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 26 Mar 2018 20:23:11 +0200 Subject: [PATCH 008/146] Set up traces in jobs feature spec --- spec/features/projects/jobs_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index aa4c2757a25..d1cee58fce5 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -113,7 +113,7 @@ feature 'Jobs' do describe "GET /:project/jobs/:id" do context "Job from project" do - let(:job) { create(:ci_build, :success, pipeline: pipeline) } + let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline) } before do visit project_job_path(project, job) @@ -136,7 +136,7 @@ feature 'Jobs' do end context 'when job is not running', :js do - let(:job) { create(:ci_build, :success, pipeline: pipeline) } + let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) } before do visit project_job_path(project, job) @@ -153,7 +153,7 @@ feature 'Jobs' do end context 'if job failed' do - let(:job) { create(:ci_build, :failed, pipeline: pipeline) } + let(:job) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) } before do visit project_job_path(project, job) @@ -339,7 +339,7 @@ feature 'Jobs' do context 'job is successfull and has deployment' do let(:deployment) { create(:deployment) } - let(:job) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) } + let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, deployments: [deployment], pipeline: pipeline) } it 'shows a link for the job' do visit project_job_path(project, job) @@ -349,7 +349,7 @@ feature 'Jobs' do end context 'job is complete and not successful' do - let(:job) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) } + let(:job) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) } it 'shows a link for the job' do visit project_job_path(project, job) @@ -360,7 +360,7 @@ feature 'Jobs' do context 'job creates a new deployment' do let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } - let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } + let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, pipeline: pipeline) } it 'shows a link to latest deployment' do visit project_job_path(project, job) From 6df1eb14fa02268f16b961d15fc43b7b03586778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 26 Mar 2018 20:47:06 +0200 Subject: [PATCH 009/146] Use Gitlab::Ci::Status#illustration in job empty_states partial --- .../projects/jobs/_empty_states.html.haml | 38 +++++-------------- spec/features/projects/jobs_spec.rb | 18 +++++++++ 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml index 9e144dbfe32..6d4a9931923 100644 --- a/app/views/projects/jobs/_empty_states.html.haml +++ b/app/views/projects/jobs/_empty_states.html.haml @@ -1,29 +1,9 @@ -- if @build.playable? - = render 'empty_state', - illustration: 'illustrations/manual_action.svg', - illustration_size: 'svg-394', - title: _('This job requires a manual action'), - content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), - action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), method: :post, class: 'btn btn-primary', title: _('Trigger this manual action') ) -- elsif @build.created? - = render 'empty_state', - illustration: 'illustrations/job_not_triggered.svg', - illustration_size: 'svg-306', - title: _('This job has not been triggered yet'), - content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered') -- elsif @build.canceled? - = render 'empty_state', - illustration: 'illustrations/canceled-job_empty.svg', - illustration_size: 'svg-430', - title: _('This job has been canceled') -- elsif @build.skipped? - = render 'empty_state', - illustration: 'illustrations/skipped-job_empty.svg', - illustration_size: 'svg-430', - title: _('This job has been skipped') -- else - = render 'empty_state', - illustration: 'illustrations/pending_job_empty.svg', - illustration_size: 'svg-430', - title: _('This job has not started yet'), - content: _('This job is in pending state and is waiting to be picked by a runner') +- detailed_status = @build.detailed_status(current_user) +- illustration = detailed_status.illustration + += render 'empty_state', + illustration: illustration[:image], + illustration_size: illustration[:size], + title: illustration[:title], + content: illustration[:content], + action: @build.playable? ? link_to(_("Trigger this manual action"), detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: _("Trigger this manual action")) : nil diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index d1cee58fce5..749a1b81872 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -379,6 +379,7 @@ feature 'Jobs' do end it 'shows manual action empty state' do + expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This job requires a manual action') expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments') expect(page).to have_link('Trigger this manual action') @@ -402,6 +403,7 @@ feature 'Jobs' do end it 'shows empty state' do + expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This job has not been triggered yet') expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered') end @@ -415,6 +417,7 @@ feature 'Jobs' do end it 'shows pending empty state' do + expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This job has not started yet') expect(page).to have_content('This job is in pending state and is waiting to be picked by a runner') end @@ -441,11 +444,26 @@ feature 'Jobs' do end it 'renders empty state' do + expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).not_to have_selector('.js-build-output') expect(page).to have_content('This job has been canceled') end end end + + context 'Skipped job' do + let(:job) { create(:ci_build, :skipped, pipeline: pipeline) } + + before do + visit project_job_path(project, job) + end + + it 'renders empty state' do + expect(page).to have_content(job.detailed_status(user).illustration[:title]) + expect(page).not_to have_selector('.js-build-output') + expect(page).to have_content('This job has been skipped') + end + end end describe "POST /:project/jobs/:id/cancel", :js do From 0969f198496c2ab0b4be6dcd0d9c6434f71e780d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 28 Mar 2018 13:31:53 +0200 Subject: [PATCH 010/146] Move illustration to build specific statuses --- lib/gitlab/ci/status/build/action.rb | 9 +++++++++ lib/gitlab/ci/status/build/canceled.rb | 21 +++++++++++++++++++++ lib/gitlab/ci/status/build/created.rb | 22 ++++++++++++++++++++++ lib/gitlab/ci/status/build/factory.rb | 7 ++++++- lib/gitlab/ci/status/build/pending.rb | 22 ++++++++++++++++++++++ lib/gitlab/ci/status/build/skipped.rb | 21 +++++++++++++++++++++ lib/gitlab/ci/status/canceled.rb | 8 -------- lib/gitlab/ci/status/created.rb | 9 --------- lib/gitlab/ci/status/manual.rb | 9 --------- lib/gitlab/ci/status/pending.rb | 9 --------- lib/gitlab/ci/status/skipped.rb | 8 -------- 11 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 lib/gitlab/ci/status/build/canceled.rb create mode 100644 lib/gitlab/ci/status/build/created.rb create mode 100644 lib/gitlab/ci/status/build/pending.rb create mode 100644 lib/gitlab/ci/status/build/skipped.rb diff --git a/lib/gitlab/ci/status/build/action.rb b/lib/gitlab/ci/status/build/action.rb index 6c9125647ad..17eb37448cb 100644 --- a/lib/gitlab/ci/status/build/action.rb +++ b/lib/gitlab/ci/status/build/action.rb @@ -14,6 +14,15 @@ module Gitlab end end + def illustration + { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: _('This job requires a manual action'), + content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), + } + end + def self.matches?(build, user) build.playable? end diff --git a/lib/gitlab/ci/status/build/canceled.rb b/lib/gitlab/ci/status/build/canceled.rb new file mode 100644 index 00000000000..c83e2734a73 --- /dev/null +++ b/lib/gitlab/ci/status/build/canceled.rb @@ -0,0 +1,21 @@ +module Gitlab + module Ci + module Status + module Build + class Canceled < Status::Extended + def illustration + { + image: 'illustrations/canceled-job_empty.svg', + size: 'svg-430', + title: _('This job has been canceled') + } + end + + def self.matches?(build, user) + build.canceled? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/build/created.rb b/lib/gitlab/ci/status/build/created.rb new file mode 100644 index 00000000000..5be8e9de425 --- /dev/null +++ b/lib/gitlab/ci/status/build/created.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + module Status + module Build + class Created < Status::Extended + def illustration + { + image: 'illustrations/job_not_triggered.svg', + size: 'svg-306', + title: _('This job has not been triggered yet'), + content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered') + } + end + + def self.matches?(build, user) + build.created? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index c852d607373..ae4b2bd79bc 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -9,7 +9,12 @@ module Gitlab [Status::Build::FailedAllowed, Status::Build::Play, Status::Build::Stop], - [Status::Build::Action]] + [Status::Build::Action], + [Status::Build::Action, + Status::Build::Canceled, + Status::Build::Created, + Status::Build::Pending, + Status::Build::Skipped]] end def self.common_helpers diff --git a/lib/gitlab/ci/status/build/pending.rb b/lib/gitlab/ci/status/build/pending.rb new file mode 100644 index 00000000000..9dd9a27ad57 --- /dev/null +++ b/lib/gitlab/ci/status/build/pending.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + module Status + module Build + class Pending < Status::Extended + def illustration + { + image: 'illustrations/pending_job_empty.svg', + size: 'svg-430', + title: _('This job has not started yet'), + content: _('This job is in pending state and is waiting to be picked by a runner') + } + end + + def self.matches?(build, user) + build.pending? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/build/skipped.rb b/lib/gitlab/ci/status/build/skipped.rb new file mode 100644 index 00000000000..3e678d0baee --- /dev/null +++ b/lib/gitlab/ci/status/build/skipped.rb @@ -0,0 +1,21 @@ +module Gitlab + module Ci + module Status + module Build + class Skipped < Status::Extended + def illustration + { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: _('This job has been skipped') + } + end + + def self.matches?(build, user) + build.skipped? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb index bea8dcd5587..e6195a60d4f 100644 --- a/lib/gitlab/ci/status/canceled.rb +++ b/lib/gitlab/ci/status/canceled.rb @@ -17,14 +17,6 @@ module Gitlab def favicon 'favicon_status_canceled' end - - def illustration - { - image: 'illustrations/canceled-job_empty.svg', - size: 'svg-430', - title: _('This job has been canceled') - } - end end end end diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb index 3b2a9e5d0ef..846f00b83dd 100644 --- a/lib/gitlab/ci/status/created.rb +++ b/lib/gitlab/ci/status/created.rb @@ -17,15 +17,6 @@ module Gitlab def favicon 'favicon_status_created' end - - def illustration - { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: _('This job has not been triggered yet'), - content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered') - } - end end end end diff --git a/lib/gitlab/ci/status/manual.rb b/lib/gitlab/ci/status/manual.rb index cb587a7815e..fc387e2fd25 100644 --- a/lib/gitlab/ci/status/manual.rb +++ b/lib/gitlab/ci/status/manual.rb @@ -17,15 +17,6 @@ module Gitlab def favicon 'favicon_status_manual' end - - def illustration - { - image: 'illustrations/manual_action.svg', - size: 'svg-394', - title: _('This job requires a manual action'), - content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments') - } - end end end end diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb index 6a27a83696a..6780780db32 100644 --- a/lib/gitlab/ci/status/pending.rb +++ b/lib/gitlab/ci/status/pending.rb @@ -17,15 +17,6 @@ module Gitlab def favicon 'favicon_status_pending' end - - def illustration - { - image: 'illustrations/pending_job_empty.svg', - size: 'svg-430', - title: _('This job has not started yet'), - content: _('This job is in pending state and is waiting to be picked by a runner') - } - end end end end diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb index ff7529b5f03..0dbdc4de426 100644 --- a/lib/gitlab/ci/status/skipped.rb +++ b/lib/gitlab/ci/status/skipped.rb @@ -17,14 +17,6 @@ module Gitlab def favicon 'favicon_status_skipped' end - - def illustration - { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: _('This job has been skipped') - } - end end end end From c48f33c5bec02e8fd49326514023f6b6af66d693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 28 Mar 2018 13:51:30 +0200 Subject: [PATCH 011/146] Move action link to build extended status illustration --- app/views/projects/jobs/_empty_states.html.haml | 2 +- lib/gitlab/ci/status/build/action.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml index 6d4a9931923..4d2c776e538 100644 --- a/app/views/projects/jobs/_empty_states.html.haml +++ b/app/views/projects/jobs/_empty_states.html.haml @@ -6,4 +6,4 @@ illustration_size: illustration[:size], title: illustration[:title], content: illustration[:content], - action: @build.playable? ? link_to(_("Trigger this manual action"), detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: _("Trigger this manual action")) : nil + action: illustration[:action_path] ? link_to(_("Trigger this manual action"), illustration[:action_path], method: illustration[:action_method], class: 'btn btn-primary', title: _("Trigger this manual action")) : nil diff --git a/lib/gitlab/ci/status/build/action.rb b/lib/gitlab/ci/status/build/action.rb index 17eb37448cb..f7f3d47c098 100644 --- a/lib/gitlab/ci/status/build/action.rb +++ b/lib/gitlab/ci/status/build/action.rb @@ -20,6 +20,8 @@ module Gitlab size: 'svg-394', title: _('This job requires a manual action'), content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), + action_path: action_path, + action_method: action_method } end From b57fcbe6162a17b02fc0516af2487ddd57c251bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 28 Mar 2018 14:57:09 +0200 Subject: [PATCH 012/146] Separate the manual empty state from the action empty state --- lib/gitlab/ci/status/build/action.rb | 11 ----------- lib/gitlab/ci/status/build/factory.rb | 2 +- lib/gitlab/ci/status/build/manual.rb | 24 ++++++++++++++++++++++++ 3 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 lib/gitlab/ci/status/build/manual.rb diff --git a/lib/gitlab/ci/status/build/action.rb b/lib/gitlab/ci/status/build/action.rb index f7f3d47c098..6c9125647ad 100644 --- a/lib/gitlab/ci/status/build/action.rb +++ b/lib/gitlab/ci/status/build/action.rb @@ -14,17 +14,6 @@ module Gitlab end end - def illustration - { - image: 'illustrations/manual_action.svg', - size: 'svg-394', - title: _('This job requires a manual action'), - content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), - action_path: action_path, - action_method: action_method - } - end - def self.matches?(build, user) build.playable? end diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index ae4b2bd79bc..004a9b305b7 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -10,7 +10,7 @@ module Gitlab Status::Build::Play, Status::Build::Stop], [Status::Build::Action], - [Status::Build::Action, + [Status::Build::Manual, Status::Build::Canceled, Status::Build::Created, Status::Build::Pending, diff --git a/lib/gitlab/ci/status/build/manual.rb b/lib/gitlab/ci/status/build/manual.rb new file mode 100644 index 00000000000..01e94e159a1 --- /dev/null +++ b/lib/gitlab/ci/status/build/manual.rb @@ -0,0 +1,24 @@ +module Gitlab + module Ci + module Status + module Build + class Manual < Status::Extended + def illustration + { + image: 'illustrations/manual_action.svg', + size: 'svg-394', + title: _('This job requires a manual action'), + content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), + action_path: play_project_job_path(subject.project, subject), + action_method: :post + } + end + + def self.matches?(build, user) + build.playable? + end + end + end + end + end +end From d6509274f32fce8b879051d19e4c9d2bb8f9d09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 28 Mar 2018 14:58:32 +0200 Subject: [PATCH 013/146] Fix tests for build empty state statuses --- spec/lib/gitlab/ci/status/build/canceled_spec.rb | 11 +++++++++++ spec/lib/gitlab/ci/status/build/created_spec.rb | 11 +++++++++++ spec/lib/gitlab/ci/status/build/manual_spec.rb | 13 +++++++++++++ spec/lib/gitlab/ci/status/build/pending_spec.rb | 11 +++++++++++ spec/lib/gitlab/ci/status/build/skipped_spec.rb | 11 +++++++++++ spec/lib/gitlab/ci/status/canceled_spec.rb | 4 ---- spec/lib/gitlab/ci/status/created_spec.rb | 4 ---- spec/lib/gitlab/ci/status/manual_spec.rb | 4 ---- spec/lib/gitlab/ci/status/pending_spec.rb | 4 ---- spec/lib/gitlab/ci/status/skipped_spec.rb | 4 ---- 10 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 spec/lib/gitlab/ci/status/build/canceled_spec.rb create mode 100644 spec/lib/gitlab/ci/status/build/created_spec.rb create mode 100644 spec/lib/gitlab/ci/status/build/manual_spec.rb create mode 100644 spec/lib/gitlab/ci/status/build/pending_spec.rb create mode 100644 spec/lib/gitlab/ci/status/build/skipped_spec.rb diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb new file mode 100644 index 00000000000..689b4237b6e --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Canceled do + subject do + described_class.new(double('subject')) + end + + describe '#illustration' do + it { expect(subject.illustration).to include(:image, :size, :title) } + end +end diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb new file mode 100644 index 00000000000..ace553c67a1 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/created_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Created do + subject do + described_class.new(double('subject')) + end + + describe '#illustration' do + it { expect(subject.illustration).to include(:image, :size, :title, :content) } + end +end diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb new file mode 100644 index 00000000000..1cbbbfa7570 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Manual do + subject do + user = create(:user) + build = create(:ci_build, :manual) + described_class.new(Gitlab::Ci::Status::Core.new(build, user)) + end + + describe '#illustration' do + it { expect(subject.illustration).to include(:image, :size, :title, :content, :action_path, :action_method) } + end +end diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb new file mode 100644 index 00000000000..2c114313536 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Pending do + subject do + described_class.new(double('subject')) + end + + describe '#illustration' do + it { expect(subject.illustration).to include(:image, :size, :title, :content) } + end +end diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb new file mode 100644 index 00000000000..4667d10e6f6 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Skipped do + subject do + described_class.new(double('subject')) + end + + describe '#illustration' do + it { expect(subject.illustration).to include(:image, :size, :title) } + end +end diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 70cf2c1d300..dc74d7e28c5 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -21,10 +21,6 @@ describe Gitlab::Ci::Status::Canceled do it { expect(subject.favicon).to eq 'favicon_status_canceled' } end - describe '#illustration' do - it { expect(subject.illustration).to include(:image, :size, :title) } - end - describe '#group' do it { expect(subject.group).to eq 'canceled' } end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index 8a7771e0dc8..ce4333f2aca 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -21,10 +21,6 @@ describe Gitlab::Ci::Status::Created do it { expect(subject.favicon).to eq 'favicon_status_created' } end - describe '#illustration' do - it { expect(subject.illustration).to include(:image, :size, :title, :content) } - end - describe '#group' do it { expect(subject.group).to eq 'created' } end diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb index 9b2dffe53d0..0463f2e1aff 100644 --- a/spec/lib/gitlab/ci/status/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/manual_spec.rb @@ -21,10 +21,6 @@ describe Gitlab::Ci::Status::Manual do it { expect(subject.favicon).to eq 'favicon_status_manual' } end - describe '#illustration' do - it { expect(subject.illustration).to include(:image, :size, :title, :content) } - end - describe '#group' do it { expect(subject.group).to eq 'manual' } end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index d859371df9d..0e25358dd8a 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -21,10 +21,6 @@ describe Gitlab::Ci::Status::Pending do it { expect(subject.favicon).to eq 'favicon_status_pending' } end - describe '#illustration' do - it { expect(subject.illustration).to include(:image, :size, :title, :content) } - end - describe '#group' do it { expect(subject.group).to eq 'pending' } end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index f7fa8a035f2..63694ca0ea6 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -21,10 +21,6 @@ describe Gitlab::Ci::Status::Skipped do it { expect(subject.favicon).to eq 'favicon_status_skipped' } end - describe '#illustration' do - it { expect(subject.illustration).to include(:image, :size, :title) } - end - describe '#group' do it { expect(subject.group).to eq 'skipped' } end From bb0483dc2f9501461407766f74600e0f3d283686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 28 Mar 2018 16:18:06 +0200 Subject: [PATCH 014/146] Move the empty state extended status to be the beginning --- lib/gitlab/ci/status/build/factory.rb | 14 +++++++------- .../lib/gitlab/ci/status/build/factory_spec.rb | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index 004a9b305b7..b809a9cf766 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -4,17 +4,17 @@ module Gitlab module Build class Factory < Status::Factory def self.extended_statuses - [[Status::Build::Cancelable, + [[Status::Build::Manual, + Status::Build::Canceled, + Status::Build::Created, + Status::Build::Pending, + Status::Build::Skipped], + [Status::Build::Cancelable, Status::Build::Retryable], [Status::Build::FailedAllowed, Status::Build::Play, Status::Build::Stop], - [Status::Build::Action], - [Status::Build::Manual, - Status::Build::Canceled, - Status::Build::Created, - Status::Build::Pending, - Status::Build::Skipped]] + [Status::Build::Action]] end def self.common_helpers diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 0c9a751561f..a68fed0a41c 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -104,7 +104,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Retryable] + .to eq [Gitlab::Ci::Status::Build::Canceled, Gitlab::Ci::Status::Build::Retryable] end it 'fabricates a retryable build status' do @@ -157,7 +157,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Cancelable] + .to eq [Gitlab::Ci::Status::Build::Pending, Gitlab::Ci::Status::Build::Cancelable] end it 'fabricates a cancelable build status' do @@ -182,12 +182,12 @@ describe Gitlab::Ci::Status::Build::Factory do expect(factory.core_status).to be_a Gitlab::Ci::Status::Skipped end - it 'does not match extended statuses' do - expect(factory.extended_statuses).to be_empty + it 'matches correct extended statuses' do + expect(factory.extended_statuses).to eq [Gitlab::Ci::Status::Build::Skipped] end - it 'fabricates a core skipped status' do - expect(status).to be_a Gitlab::Ci::Status::Skipped + it 'fabricates a skipped build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Skipped end it 'fabricates status with correct details' do @@ -211,7 +211,8 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Play, + .to eq [Gitlab::Ci::Status::Build::Manual, + Gitlab::Ci::Status::Build::Play, Gitlab::Ci::Status::Build::Action] end @@ -259,7 +260,8 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Stop, + .to eq [Gitlab::Ci::Status::Build::Manual, + Gitlab::Ci::Status::Build::Stop, Gitlab::Ci::Status::Build::Action] end From ddabac46b918611ecaa82ea5af554a7f0f9b7407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 29 Mar 2018 19:24:19 +0200 Subject: [PATCH 015/146] Move action button titles to respective statuses --- app/views/projects/jobs/_empty_states.html.haml | 2 +- lib/gitlab/ci/status/build/cancelable.rb | 4 ++++ lib/gitlab/ci/status/build/manual.rb | 4 +--- lib/gitlab/ci/status/build/play.rb | 4 ++++ lib/gitlab/ci/status/build/retryable.rb | 4 ++++ lib/gitlab/ci/status/build/stop.rb | 4 ++++ spec/lib/gitlab/ci/status/build/cancelable_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/build/manual_spec.rb | 2 +- spec/lib/gitlab/ci/status/build/play_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/build/retryable_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/build/stop_spec.rb | 4 ++++ 11 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml index 4d2c776e538..e5198d047df 100644 --- a/app/views/projects/jobs/_empty_states.html.haml +++ b/app/views/projects/jobs/_empty_states.html.haml @@ -6,4 +6,4 @@ illustration_size: illustration[:size], title: illustration[:title], content: illustration[:content], - action: illustration[:action_path] ? link_to(_("Trigger this manual action"), illustration[:action_path], method: illustration[:action_method], class: 'btn btn-primary', title: _("Trigger this manual action")) : nil + action: detailed_status.has_action? ? link_to(detailed_status.action_button_title, detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: detailed_status.action_button_title) : nil diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb index 2d9166d6bdd..024047d4983 100644 --- a/lib/gitlab/ci/status/build/cancelable.rb +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -23,6 +23,10 @@ module Gitlab 'Cancel' end + def action_button_title + _('Cancel this job') + end + def self.matches?(build, user) build.cancelable? end diff --git a/lib/gitlab/ci/status/build/manual.rb b/lib/gitlab/ci/status/build/manual.rb index 01e94e159a1..042da6392d3 100644 --- a/lib/gitlab/ci/status/build/manual.rb +++ b/lib/gitlab/ci/status/build/manual.rb @@ -8,9 +8,7 @@ module Gitlab image: 'illustrations/manual_action.svg', size: 'svg-394', title: _('This job requires a manual action'), - content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments'), - action_path: play_project_job_path(subject.project, subject), - action_method: :post + content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments') } end diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index b7b45466d3b..a8b9ebf0803 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -19,6 +19,10 @@ module Gitlab 'Play' end + def action_button_title + _('Trigger this manual action') + end + def action_path play_project_job_path(subject.project, subject) end diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb index 44ffe783e50..5aeb8e51480 100644 --- a/lib/gitlab/ci/status/build/retryable.rb +++ b/lib/gitlab/ci/status/build/retryable.rb @@ -15,6 +15,10 @@ module Gitlab 'Retry' end + def action_button_title + _('Retry this job') + end + def action_path retry_project_job_path(subject.project, subject) end diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index 46e730797e4..dea838bfa39 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -19,6 +19,10 @@ module Gitlab 'Stop' end + def action_button_title + _('Stop this environment') + end + def action_path play_project_job_path(subject.project, subject) end diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb index 9cdebaa5cf2..0b88e0c7c9c 100644 --- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -72,6 +72,10 @@ describe Gitlab::Ci::Status::Build::Cancelable do describe '#action_title' do it { expect(subject.action_title).to eq 'Cancel' } end + + describe '#action_button_title' do + it { expect(subject.action_button_title).to eq 'Cancel this job' } + end end describe '.matches?' do diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb index 1cbbbfa7570..c8c650d4e56 100644 --- a/spec/lib/gitlab/ci/status/build/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb @@ -8,6 +8,6 @@ describe Gitlab::Ci::Status::Build::Manual do end describe '#illustration' do - it { expect(subject.illustration).to include(:image, :size, :title, :content, :action_path, :action_method) } + it { expect(subject.illustration).to include(:image, :size, :title, :content) } end end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 81d5f553fd1..b0c3a8bb398 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -53,6 +53,10 @@ describe Gitlab::Ci::Status::Build::Play do it { expect(subject.action_title).to eq 'Play' } end + describe '#action_button_title' do + it { expect(subject.action_button_title).to eq 'Trigger this manual action' } + end + describe '.matches?' do subject { described_class.matches?(build, user) } diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb index 14d42e0d70f..d6e909f6268 100644 --- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -72,6 +72,10 @@ describe Gitlab::Ci::Status::Build::Retryable do describe '#action_title' do it { expect(subject.action_title).to eq 'Retry' } end + + describe '#action_button_title' do + it { expect(subject.action_button_title).to eq 'Retry this job' } + end end describe '.matches?' do diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index 18e250772f0..7a72ca2c14d 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -44,6 +44,10 @@ describe Gitlab::Ci::Status::Build::Stop do describe '#action_title' do it { expect(subject.action_title).to eq 'Stop' } end + + describe '#action_button_title' do + it { expect(subject.action_button_title).to eq 'Stop this environment' } + end end describe '.matches?' do From 3447d71e73a538dbcb30fb478474bdd526ff38a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 29 Mar 2018 19:39:30 +0200 Subject: [PATCH 016/146] Add specs for empty states .matches? methods --- .../gitlab/ci/status/build/canceled_spec.rb | 22 ++++++++++++++++++ .../gitlab/ci/status/build/created_spec.rb | 22 ++++++++++++++++++ .../lib/gitlab/ci/status/build/manual_spec.rb | 23 ++++++++++++++++++- .../gitlab/ci/status/build/pending_spec.rb | 22 ++++++++++++++++++ .../gitlab/ci/status/build/skipped_spec.rb | 22 ++++++++++++++++++ 5 files changed, 110 insertions(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb index 689b4237b6e..c6b5cc68770 100644 --- a/spec/lib/gitlab/ci/status/build/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Canceled do + let(:user) { create(:user) } + subject do described_class.new(double('subject')) end @@ -8,4 +10,24 @@ describe Gitlab::Ci::Status::Build::Canceled do describe '#illustration' do it { expect(subject.illustration).to include(:image, :size, :title) } end + + describe '.matches?' do + subject {described_class.matches?(build, user) } + + context 'when build is canceled' do + let(:build) { create(:ci_build, :canceled) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not canceled' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(subject).to be false + end + end + end end diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb index ace553c67a1..8bdfe6ef7a2 100644 --- a/spec/lib/gitlab/ci/status/build/created_spec.rb +++ b/spec/lib/gitlab/ci/status/build/created_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Created do + let(:user) { create(:user) } + subject do described_class.new(double('subject')) end @@ -8,4 +10,24 @@ describe Gitlab::Ci::Status::Build::Created do describe '#illustration' do it { expect(subject.illustration).to include(:image, :size, :title, :content) } end + + describe '.matches?' do + subject {described_class.matches?(build, user) } + + context 'when build is created' do + let(:build) { create(:ci_build, :created) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not created' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(subject).to be false + end + end + end end diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb index c8c650d4e56..6386296f992 100644 --- a/spec/lib/gitlab/ci/status/build/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Manual do + let(:user) { create(:user) } + subject do - user = create(:user) build = create(:ci_build, :manual) described_class.new(Gitlab::Ci::Status::Core.new(build, user)) end @@ -10,4 +11,24 @@ describe Gitlab::Ci::Status::Build::Manual do describe '#illustration' do it { expect(subject.illustration).to include(:image, :size, :title, :content) } end + + describe '.matches?' do + subject {described_class.matches?(build, user) } + + context 'when build is manual' do + let(:build) { create(:ci_build, :manual) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not manual' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(subject).to be false + end + end + end end diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb index 2c114313536..4cf70828e53 100644 --- a/spec/lib/gitlab/ci/status/build/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Pending do + let(:user) { create(:user) } + subject do described_class.new(double('subject')) end @@ -8,4 +10,24 @@ describe Gitlab::Ci::Status::Build::Pending do describe '#illustration' do it { expect(subject.illustration).to include(:image, :size, :title, :content) } end + + describe '.matches?' do + subject {described_class.matches?(build, user) } + + context 'when build is pending' do + let(:build) { create(:ci_build, :pending) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not pending' do + let(:build) { create(:ci_build, :success) } + + it 'does not match' do + expect(subject).to be false + end + end + end end diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb index 4667d10e6f6..46f6933025a 100644 --- a/spec/lib/gitlab/ci/status/build/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Skipped do + let(:user) { create(:user) } + subject do described_class.new(double('subject')) end @@ -8,4 +10,24 @@ describe Gitlab::Ci::Status::Build::Skipped do describe '#illustration' do it { expect(subject.illustration).to include(:image, :size, :title) } end + + describe '.matches?' do + subject {described_class.matches?(build, user) } + + context 'when build is skipped' do + let(:build) { create(:ci_build, :skipped) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not skipped' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(subject).to be false + end + end + end end From 8a4ec84965bb746c86e7e73aa2f9abb238d85ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 3 Apr 2018 16:54:03 +0200 Subject: [PATCH 017/146] Add action_button_title Core status method --- lib/gitlab/ci/status/core.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 89fb9a0b7bc..031bd4cd29c 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -61,6 +61,10 @@ module Gitlab def action_title raise NotImplementedError end + + def action_button_title + raise NotImplementedError + end end end end From 6c55a9d8f29a4d934dab2c3294c319bb8592fe2b Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 29 Mar 2018 19:37:00 +0200 Subject: [PATCH 018/146] Remove support for legacy tar.gz pages artifacts --- app/services/projects/update_pages_service.rb | 14 +- .../projects/update_pages_service_spec.rb | 120 +++++++++--------- 2 files changed, 59 insertions(+), 75 deletions(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 7e228d1833d..de77f6bf585 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -74,25 +74,13 @@ module Projects end def extract_archive!(temp_path) - if artifacts.ends_with?('.tar.gz') || artifacts.ends_with?('.tgz') - extract_tar_archive!(temp_path) - elsif artifacts.ends_with?('.zip') + if artifacts.ends_with?('.zip') extract_zip_archive!(temp_path) else raise InvaildStateError, 'unsupported artifacts format' end end - def extract_tar_archive!(temp_path) - build.artifacts_file.use_file do |artifacts_path| - results = Open3.pipeline(%W(gunzip -c #{artifacts_path}), - %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), - %W(tar -x -C #{temp_path} #{SITE_PATH}), - err: '/dev/null') - raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?) - end - end - def extract_zip_archive!(temp_path) raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata? diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index dd31a677dfe..1b6caeab15d 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -21,76 +21,72 @@ describe Projects::UpdatePagesService do end context 'legacy artifacts' do - %w(tar.gz zip).each do |format| - let(:extension) { format } + let(:extension) { 'zip' } - context "for valid #{format}" do + before do + build.update_attributes(legacy_artifacts_file: file) + build.update_attributes(legacy_artifacts_metadata: metadata) + end + + describe 'pages artifacts' do + context 'with expiry date' do before do - build.update_attributes(legacy_artifacts_file: file) - build.update_attributes(legacy_artifacts_metadata: metadata) + build.artifacts_expire_in = "2 days" + build.save! end - describe 'pages artifacts' do - context 'with expiry date' do - before do - build.artifacts_expire_in = "2 days" - build.save! - end - - it "doesn't delete artifacts" do - expect(execute).to eq(:success) - - expect(build.reload.artifacts?).to eq(true) - end - end - - context 'without expiry date' do - it "does delete artifacts" do - expect(execute).to eq(:success) - - expect(build.reload.artifacts?).to eq(false) - end - end - end - - it 'succeeds' do - expect(project.pages_deployed?).to be_falsey + it "doesn't delete artifacts" do expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - # Check that all expected files are extracted - %w[index.html zero .hidden/file].each do |filename| - expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy - end - end - - it 'limits pages size' do - stub_application_setting(max_pages_size: 1) - expect(execute).not_to eq(:success) - end - - it 'removes pages after destroy' do - expect(PagesWorker).to receive(:perform_in) - expect(project.pages_deployed?).to be_falsey - expect(execute).to eq(:success) - expect(project.pages_deployed?).to be_truthy - project.destroy - expect(project.pages_deployed?).to be_falsey - end - - it 'fails if sha on branch is not latest' do - build.update_attributes(ref: 'feature') - - expect(execute).not_to eq(:success) - end - - it 'fails for empty file fails' do - build.update_attributes(legacy_artifacts_file: empty_file) - - expect { execute } - .to raise_error(Projects::UpdatePagesService::FailedToExtractError) + expect(build.reload.artifacts?).to eq(true) end end + + context 'without expiry date' do + it "does delete artifacts" do + expect(execute).to eq(:success) + + expect(build.reload.artifacts?).to eq(false) + end + end + end + + it 'succeeds' do + expect(project.pages_deployed?).to be_falsey + expect(execute).to eq(:success) + expect(project.pages_deployed?).to be_truthy + + # Check that all expected files are extracted + %w[index.html zero .hidden/file].each do |filename| + expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy + end + end + + it 'limits pages size' do + stub_application_setting(max_pages_size: 1) + expect(execute).not_to eq(:success) + end + + it 'removes pages after destroy' do + expect(PagesWorker).to receive(:perform_in) + expect(project.pages_deployed?).to be_falsey + expect(execute).to eq(:success) + expect(project.pages_deployed?).to be_truthy + project.destroy + expect(project.pages_deployed?).to be_falsey + end + + it 'fails if sha on branch is not latest' do + build.update_attributes(ref: 'feature') + + expect(execute).not_to eq(:success) + end + + it 'fails for empty file fails' do + build.update_attributes(legacy_artifacts_file: empty_file) + + expect { execute } + .to raise_error(Projects::UpdatePagesService::FailedToExtractError) end end From 8ac7ea4301eb9c1c90a592e878ac32154f6eb354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 3 Apr 2018 19:47:04 +0200 Subject: [PATCH 019/146] Set up traces in build show spec --- spec/views/projects/jobs/show.html.haml_spec.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb index 6a67da79ec5..64d6ae6d2a3 100644 --- a/spec/views/projects/jobs/show.html.haml_spec.rb +++ b/spec/views/projects/jobs/show.html.haml_spec.rb @@ -18,7 +18,7 @@ describe 'projects/jobs/show' do describe 'environment info in job view' do context 'job with latest deployment' do let(:build) do - create(:ci_build, :success, environment: 'staging') + create(:ci_build, :success, :trace_artifact, environment: 'staging') end before do @@ -37,11 +37,11 @@ describe 'projects/jobs/show' do context 'job with outdated deployment' do let(:build) do - create(:ci_build, :success, environment: 'staging', pipeline: pipeline) + create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) end let(:second_build) do - create(:ci_build, :success, environment: 'staging', pipeline: pipeline) + create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) end let(:environment) do @@ -67,7 +67,7 @@ describe 'projects/jobs/show' do context 'job failed to deploy' do let(:build) do - create(:ci_build, :failed, environment: 'staging', pipeline: pipeline) + create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline) end let!(:environment) do @@ -85,7 +85,7 @@ describe 'projects/jobs/show' do context 'job will deploy' do let(:build) do - create(:ci_build, :running, environment: 'staging', pipeline: pipeline) + create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline) end context 'when environment exists' do @@ -133,7 +133,7 @@ describe 'projects/jobs/show' do context 'job that failed to deploy and environment has not been created' do let(:build) do - create(:ci_build, :failed, environment: 'staging', pipeline: pipeline) + create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline) end let!(:environment) do @@ -151,7 +151,7 @@ describe 'projects/jobs/show' do context 'job that will deploy and environment has not been created' do let(:build) do - create(:ci_build, :running, environment: 'staging', pipeline: pipeline) + create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline) end let!(:environment) do @@ -171,8 +171,9 @@ describe 'projects/jobs/show' do end context 'when job is running' do + let(:build) { create(:ci_build, :trace_live, :running, pipeline: pipeline) } + before do - build.run! render end From 5af00309d3a4d50e89b278981dcd6485ae4e77f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 3 Apr 2018 20:14:33 +0200 Subject: [PATCH 020/146] Properly set up jobs in mini pipeline graph spec --- .../merge_request/user_sees_mini_pipeline_graph_spec.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb index a43ba05c64c..fd1629746ef 100644 --- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb +++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb @@ -9,6 +9,7 @@ describe 'Merge request < User sees mini pipeline graph', :js do before do build.run + build.trace.set('hello') sign_in(user) visit_merge_request end @@ -26,15 +27,15 @@ describe 'Merge request < User sees mini pipeline graph', :js do let(:artifacts_file2) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') } before do - create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file1) - create(:ci_build, pipeline: pipeline, when: 'manual') + create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1) + create(:ci_build, :manual, pipeline: pipeline, when: 'manual') end it 'avoids repeated database queries' do before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } - create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file2) - create(:ci_build, pipeline: pipeline, when: 'manual') + create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2) + create(:ci_build, :manual, pipeline: pipeline, when: 'manual') after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') } From ff1383fb11267996bf040260513e6d3d8f468def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 3 Apr 2018 20:17:26 +0200 Subject: [PATCH 021/146] Properly set up job trace in user_browses_job_spec --- spec/features/projects/jobs/user_browses_job_spec.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 4c49cff30d4..8fa902cc386 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -1,16 +1,14 @@ require 'spec_helper' describe 'User browses a job', :js do - let!(:build) { create(:ci_build, :running, :coverage, pipeline: pipeline) } - let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') } - let(:project) { create(:project, :repository, namespace: user.namespace) } let(:user) { create(:user) } + let(:project) { create(:project, :repository, namespace: user.namespace) } + let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') } + let!(:build) { create(:ci_build, :success, :trace_artifact, :coverage, pipeline: pipeline) } before do project.add_master(user) project.enable_ci - build.success - build.trace.set('job trace') sign_in(user) From c1b71e2fa1e49da82b15ee7f12148a372face09c Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Fri, 23 Mar 2018 15:27:15 +0100 Subject: [PATCH 022/146] Check if at least one filter is set on dashboard When listing issues and merge requests on dasboard page, make sure that at least one filter is enabled. User's id is used in search autocomplete widget instead of username, which allows presetting user in filter dropdowns. Related to #43246 --- app/assets/javascripts/search_autocomplete.js | 8 +++---- app/controllers/dashboard_controller.rb | 14 +++++++++++ app/helpers/application_helper.rb | 2 -- app/helpers/issuables_helper.rb | 24 +++++-------------- app/views/dashboard/issues.html.haml | 2 +- app/views/dashboard/merge_requests.html.haml | 2 +- app/views/shared/issuable/_filter.html.haml | 4 ---- app/views/shared/issuable/_nav.html.haml | 11 +++++---- spec/controllers/dashboard_controller_spec.rb | 2 ++ spec/features/dashboard/issues_spec.rb | 9 ------- ...issuables_list_metadata_shared_examples.rb | 8 +++---- ...uables_requiring_filter_shared_examples.rb | 15 ++++++++++++ 12 files changed, 53 insertions(+), 48 deletions(-) create mode 100644 spec/support/issuables_requiring_filter_shared_examples.rb diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 7dd3e9858c6..2da022fde63 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -233,21 +233,21 @@ export default class SearchAutocomplete { const issueItems = [ { text: 'Issues assigned to me', - url: `${issuesPath}/?assignee_username=${userName}`, + url: `${issuesPath}/?assignee_id=${userId}`, }, { text: "Issues I've created", - url: `${issuesPath}/?author_username=${userName}`, + url: `${issuesPath}/?author_id=${userId}`, }, ]; const mergeRequestItems = [ { text: 'Merge requests assigned to me', - url: `${mrPath}/?assignee_username=${userName}`, + url: `${mrPath}/?assignee_id=${userId}`, }, { text: "Merge requests I've created", - url: `${mrPath}/?author_username=${userName}`, + url: `${mrPath}/?author_id=${userId}`, }, ]; diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 280ed93faf8..d228956d8e3 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -2,9 +2,17 @@ class DashboardController < Dashboard::ApplicationController include IssuesAction include MergeRequestsAction + FILTER_PARAMS = [ + :author_id, + :assignee_id, + :milestone_title, + :label_name + ].freeze + before_action :event_filter, only: :activity before_action :projects, only: [:issues, :merge_requests] before_action :set_show_full_reference, only: [:issues, :merge_requests] + before_action :check_filters_presence, only: [:issues, :merge_requests] respond_to :html @@ -39,4 +47,10 @@ class DashboardController < Dashboard::ApplicationController def set_show_full_reference @show_full_reference = true end + + def check_filters_presence + @no_filters_set = FILTER_PARAMS.none? { |k| params.key?(k) } + + render action: action_name if @no_filters_set + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 86ec500ceb3..228c8d2e8f9 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -228,9 +228,7 @@ module ApplicationHelper scope: params[:scope], milestone_title: params[:milestone_title], assignee_id: params[:assignee_id], - assignee_username: params[:assignee_username], author_id: params[:author_id], - author_username: params[:author_username], search: params[:search], label_name: params[:label_name] } diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 6d6b840f485..06c3e569c84 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -159,16 +159,18 @@ module IssuablesHelper label_names.join(', ') end - def issuables_state_counter_text(issuable_type, state) + def issuables_state_counter_text(issuable_type, state, display_count) titles = { opened: "Open" } state_title = titles[state] || state.to_s.humanize - count = issuables_count_for_state(issuable_type, state) - html = content_tag(:span, state_title) - html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') + + if display_count + count = issuables_count_for_state(issuable_type, state) + html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') + end html.html_safe end @@ -191,24 +193,10 @@ module IssuablesHelper end end - def issuable_filter_params - [ - :search, - :author_id, - :assignee_id, - :milestone_title, - :label_name - ] - end - def issuable_reference(issuable) @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project) end - def issuable_filter_present? - issuable_filter_params.any? { |k| params.key?(k) } - end - def issuable_initial_data(issuable) data = { endpoint: issuable_path(issuable), diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 3e85535dae0..24d69b56c20 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -5,7 +5,7 @@ = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues") .top-area - = render 'shared/issuable/nav', type: :issues + = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set .nav-controls = link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do = icon('rss') diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 53cd1130299..fb57ec7835c 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -3,7 +3,7 @@ - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) .top-area - = render 'shared/issuable/nav', type: :merge_requests + = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set .nav-controls = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 7704c88905b..58b37290cd5 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -24,10 +24,6 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - - if issuable_filter_present? - .filter-item.inline.reset-filters - %a{ href: page_filter_path(without: issuable_filter_params) } Reset filters - .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index 4d8109eb90c..a5f40ea934b 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -1,22 +1,23 @@ - type = local_assigns.fetch(:type, :issues) - page_context_word = type.to_s.humanize(capitalize: false) +- display_count = local_assigns.fetch(:display_count, :true) %ul.nav-links.issues-state-filters.mobile-separator %li{ class: active_when(params[:state] == 'opened') }> = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do - #{issuables_state_counter_text(type, :opened)} + #{issuables_state_counter_text(type, :opened, display_count)} - if type == :merge_requests %li{ class: active_when(params[:state] == 'merged') }> = link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do - #{issuables_state_counter_text(type, :merged)} + #{issuables_state_counter_text(type, :merged, display_count)} %li{ class: active_when(params[:state] == 'closed') }> = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do - #{issuables_state_counter_text(type, :closed)} + #{issuables_state_counter_text(type, :closed, display_count)} - else %li{ class: active_when(params[:state] == 'closed') }> = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do - #{issuables_state_counter_text(type, :closed)} + #{issuables_state_counter_text(type, :closed, display_count)} - = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all) + = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count) diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb index 97c2c3fb940..3458d679107 100644 --- a/spec/controllers/dashboard_controller_spec.rb +++ b/spec/controllers/dashboard_controller_spec.rb @@ -11,9 +11,11 @@ describe DashboardController do describe 'GET issues' do it_behaves_like 'issuables list meta-data', :issue, :issues + it_behaves_like 'issuables requiring filter', :issues end describe 'GET merge requests' do it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests + it_behaves_like 'issuables requiring filter', :merge_requests end end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 8d1d5a51750..e41a2e4ce09 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -51,15 +51,6 @@ RSpec.describe 'Dashboard Issues' do expect(page).not_to have_content(other_issue.title) end - it 'shows all issues' do - click_link('Reset filters') - - expect(page).to have_content(authored_issue.title) - expect(page).to have_content(authored_issue_on_public_project.title) - expect(page).to have_content(assigned_issue.title) - expect(page).to have_content(other_issue.title) - end - it 'state filter tabs work' do find('#state-closed').click expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true) diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb index 75982432ab4..e61983c60b4 100644 --- a/spec/support/issuables_list_metadata_shared_examples.rb +++ b/spec/support/issuables_list_metadata_shared_examples.rb @@ -5,9 +5,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| %w[fix improve/awesome].each do |source_branch| issuable = if issuable_type == :issue - create(issuable_type, project: project) + create(issuable_type, project: project, author: project.creator) else - create(issuable_type, source_project: project, source_branch: source_branch) + create(issuable_type, source_project: project, source_branch: source_branch, author: project.creator) end @issuable_ids << issuable.id @@ -16,7 +16,7 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| it "creates indexed meta-data object for issuable notes and votes count" do if action - get action + get action, author_id: project.creator.id else get :index, namespace_id: project.namespace, project_id: project end @@ -35,7 +35,7 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| it "doesn't execute any queries with false conditions" do get_action = if action - proc { get action } + proc { get action, author_id: project.creator.id } else proc { get :index, namespace_id: project2.namespace, project_id: project2 } end diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb new file mode 100644 index 00000000000..439ef5ed92e --- /dev/null +++ b/spec/support/issuables_requiring_filter_shared_examples.rb @@ -0,0 +1,15 @@ +shared_examples 'issuables requiring filter' do |action| + it "doesn't load any issuables if no filter is set" do + expect_any_instance_of(described_class).not_to receive(:issuables_collection) + + get action + + expect(response).to render_template(action) + end + + it "loads issuables if at least one filter is set" do + expect_any_instance_of(described_class).to receive(:issuables_collection).and_call_original + + get action, author_id: user.id + end +end From a32941aee32ddfd7b114011f951741e06a53be5d Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 29 Mar 2018 13:43:53 +0200 Subject: [PATCH 023/146] Display illustration and message if no filter is selected --- app/assets/stylesheets/framework/images.scss | 2 +- app/views/dashboard/issues.html.haml | 6 +++++- app/views/dashboard/merge_requests.html.haml | 6 +++++- app/views/shared/dashboard/_no_filter_selected.html.haml | 8 ++++++++ app/views/shared/issuable/_filter.html.haml | 5 +++-- 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 app/views/shared/dashboard/_no_filter_selected.html.haml diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss index df1cafc9f8e..62a0fba3da3 100644 --- a/app/assets/stylesheets/framework/images.scss +++ b/app/assets/stylesheets/framework/images.scss @@ -20,7 +20,7 @@ width: 100%; } - $image-widths: 80 250 306 394 430; + $image-widths: 80 130 250 306 394 430; @each $width in $image-widths { &.svg-#{$width} { img, diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 24d69b56c20..35bf92a0d07 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -12,4 +12,8 @@ = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/issuable/filter', type: :issues -= render 'shared/issues' + +- if current_user && @no_filters_set + = render 'shared/dashboard/no_filter_selected' +- else + = render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index fb57ec7835c..7b69a6f5d0f 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -8,4 +8,8 @@ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests -= render 'shared/merge_requests' + +- if current_user && @no_filters_set + = render 'shared/dashboard/no_filter_selected' +- else + = render 'shared/merge_requests' diff --git a/app/views/shared/dashboard/_no_filter_selected.html.haml b/app/views/shared/dashboard/_no_filter_selected.html.haml new file mode 100644 index 00000000000..e5c2f5b2346 --- /dev/null +++ b/app/views/shared/dashboard/_no_filter_selected.html.haml @@ -0,0 +1,8 @@ +.row.empty-state.text-center + .col-xs-12 + .svg-130 + = image_tag 'illustrations/issue-dashboard_results-without-filter.svg' + .col-xs-12 + .text-content + %h4 + = _("Please select at least one filter to see results") diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 58b37290cd5..994198e2f69 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -24,8 +24,9 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - .pull-right - = render 'shared/sort_dropdown' + - if !@no_filters_set + .pull-right + = render 'shared/sort_dropdown' - has_labels = @labels && @labels.any? .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } From d10416e231a27c63c322ba7338f2ea8279436336 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Tue, 27 Mar 2018 17:29:13 +0200 Subject: [PATCH 024/146] Fixed dashboard filtering tests --- app/controllers/dashboard_controller.rb | 11 +- changelogs/unreleased/43246-checkfilter.yml | 6 + spec/features/atom/dashboard_issues_spec.rb | 17 ++- spec/features/dashboard/issues_filter_spec.rb | 32 ++--- .../features/dashboard/merge_requests_spec.rb | 8 +- .../user_uses_header_search_field_spec.rb | 127 ++++++++++++------ spec/helpers/issuables_helper_spec.rb | 29 +--- spec/javascripts/search_autocomplete_spec.js | 8 +- 8 files changed, 137 insertions(+), 101 deletions(-) create mode 100644 changelogs/unreleased/43246-checkfilter.yml diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index d228956d8e3..68d328fa797 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -12,7 +12,7 @@ class DashboardController < Dashboard::ApplicationController before_action :event_filter, only: :activity before_action :projects, only: [:issues, :merge_requests] before_action :set_show_full_reference, only: [:issues, :merge_requests] - before_action :check_filters_presence, only: [:issues, :merge_requests] + before_action :check_filters_presence!, only: [:issues, :merge_requests] respond_to :html @@ -48,9 +48,14 @@ class DashboardController < Dashboard::ApplicationController @show_full_reference = true end - def check_filters_presence + def check_filters_presence! @no_filters_set = FILTER_PARAMS.none? { |k| params.key?(k) } - render action: action_name if @no_filters_set + return unless @no_filters_set + + respond_to do |format| + format.html + format.atom { head :bad_request } + end end end diff --git a/changelogs/unreleased/43246-checkfilter.yml b/changelogs/unreleased/43246-checkfilter.yml new file mode 100644 index 00000000000..e6c0e716213 --- /dev/null +++ b/changelogs/unreleased/43246-checkfilter.yml @@ -0,0 +1,6 @@ +--- +title: Require at least one filter when listing issues or merge requests on dashboard + page +merge_request: +author: +type: performance diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index d673bac4995..fb6c71ce997 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -13,17 +13,26 @@ describe "Dashboard Issues Feed" do end describe "atom feed" do - it "renders atom feed via personal access token" do + it "returns 400 if no filter is used" do personal_access_token = create(:personal_access_token, user: user) visit issues_dashboard_path(:atom, private_token: personal_access_token.token) + expect(response_headers['Content-Type']).to have_content('application/atom+xml') + expect(page.status_code).to eq(400) + end + + it "renders atom feed via personal access token" do + personal_access_token = create(:personal_access_token, user: user) + + visit issues_dashboard_path(:atom, private_token: personal_access_token.token, assignee_id: user.id) + expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") end it "renders atom feed via RSS token" do - visit issues_dashboard_path(:atom, rss_token: user.rss_token) + visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id) expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") @@ -44,7 +53,7 @@ describe "Dashboard Issues Feed" do let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') } it "renders issue fields" do - visit issues_dashboard_path(:atom, rss_token: user.rss_token) + visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") @@ -67,7 +76,7 @@ describe "Dashboard Issues Feed" do end it "renders issue label and milestone info" do - visit issues_dashboard_path(:atom, rss_token: user.rss_token) + visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index 029fc45c791..b15f3afca4a 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -17,6 +17,12 @@ feature 'Dashboard Issues filtering', :js do visit_issues end + context 'without any filter' do + it 'shows error message' do + expect(page).to have_content 'Please select at least one filter to see results' + end + end + context 'filtering by milestone' do it 'shows all issues with no milestone' do show_milestone_dropdown @@ -27,15 +33,6 @@ feature 'Dashboard Issues filtering', :js do expect(page).to have_selector('.issue', count: 1) end - it 'shows all issues with any milestone' do - show_milestone_dropdown - - click_link 'Any Milestone' - - expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - expect(page).to have_selector('.issue', count: 2) - end - it 'shows all issues with the selected milestone' do show_milestone_dropdown @@ -68,13 +65,6 @@ feature 'Dashboard Issues filtering', :js do let(:label) { create(:label, project: project) } let!(:label_link) { create(:label_link, label: label, target: issue) } - it 'shows all issues without filter' do - page.within 'ul.content-list' do - expect(page).to have_content issue.title - expect(page).to have_content issue2.title - end - end - it 'shows all issues with the selected label' do page.within '.labels-filter' do find('.dropdown').click @@ -89,9 +79,19 @@ feature 'Dashboard Issues filtering', :js do end context 'sorting' do +<<<<<<< HEAD it 'shows sorted issues' do sort_by('Created date') visit_issues +======= + before do + visit_issues(assignee_id: user.id) + end + + it 'remembers last sorting value' do + sorting_by('Created date') + visit_issues(assignee_id: user.id) +>>>>>>> Fixed dashboard filtering tests expect(find('.issues-filters')).to have_content('Created date') end diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 4a9344115d2..0965b745c03 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -103,15 +103,11 @@ feature 'Dashboard Merge Requests' do expect(page).not_to have_content(other_merge_request.title) end - it 'shows all merge requests', :js do + it 'shows error message without filter', :js do filter_item_select('Any Assignee', '.js-assignee-search') filter_item_select('Any Author', '.js-author-search') - expect(page).to have_content(authored_merge_request.title) - expect(page).to have_content(authored_merge_request_from_fork.title) - expect(page).to have_content(assigned_merge_request.title) - expect(page).to have_content(assigned_merge_request_from_fork.title) - expect(page).to have_content(other_merge_request.title) + expect(page).to have_content('Please select at least one filter to see results') end it 'shows sorted merge requests' do diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 5ddea36add5..a9128104b87 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -9,49 +9,25 @@ describe 'User uses header search field' do before do project.add_reporter(user) sign_in(user) - - visit(project_path(project)) end - it 'starts searching by pressing the enter key', :js do - fill_in('search', with: 'gitlab') - find('#search').native.send_keys(:enter) - - page.within('.breadcrumbs-sub-title') do - expect(page).to have_content('Search') - end - end - - it 'contains location badge' do - expect(page).to have_selector('.has-location-badge') - end - - context 'when clicking the search field', :js do + context 'when user is in a global scope', :js do before do + visit(root_path) page.find('#search').click end - it 'shows category search dropdown' do - expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i) - end - context 'when clicking issues' do - let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } - it 'shows assigned issues' do - find('.dropdown-menu').click_link('Issues assigned to me') + find('.search-input-container .dropdown-menu').click_link('Issues assigned to me') - expect(page).to have_selector('.filtered-search') - expect_tokens([assignee_token(user.name)]) - expect_filtered_search_input_empty + expect(find('.js-assignee-search')).to have_content(user.name) end it 'shows created issues' do - find('.dropdown-menu').click_link("Issues I've created") + find('.search-input-container .dropdown-menu').click_link("Issues I've created") - expect(page).to have_selector('.filtered-search') - expect_tokens([author_token(user.name)]) - expect_filtered_search_input_empty + expect(find('.js-author-search')).to have_content(user.name) end end @@ -59,32 +35,97 @@ describe 'User uses header search field' do let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) } it 'shows assigned merge requests' do - find('.dropdown-menu').click_link('Merge requests assigned to me') + find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me') - expect(page).to have_selector('.merge-requests-holder') - expect_tokens([assignee_token(user.name)]) - expect_filtered_search_input_empty + expect(find('.js-assignee-search')).to have_content(user.name) end it 'shows created merge requests' do - find('.dropdown-menu').click_link("Merge requests I've created") + find('.search-input-container .dropdown-menu').click_link("Merge requests I've created") - expect(page).to have_selector('.merge-requests-holder') - expect_tokens([author_token(user.name)]) - expect_filtered_search_input_empty + expect(find('.js-author-search')).to have_content(user.name) end end end - context 'when entering text into the search field', :js do + context 'when user is in a project scope' do before do - page.within('.search-input-wrap') do - fill_in('search', with: project.name[0..3]) + visit(project_path(project)) + end + + it 'starts searching by pressing the enter key', :js do + fill_in('search', with: 'gitlab') + find('#search').native.send_keys(:enter) + + page.within('.breadcrumbs-sub-title') do + expect(page).to have_content('Search') end end - it 'does not display the category search dropdown' do - expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i) + it 'contains location badge' do + expect(page).to have_selector('.has-location-badge') + end + + context 'when clicking the search field', :js do + before do + page.find('#search').click + end + + it 'shows category search dropdown' do + expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i) + end + + context 'when clicking issues' do + let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + + it 'shows assigned issues' do + find('.dropdown-menu').click_link('Issues assigned to me') + + expect(page).to have_selector('.filtered-search') + expect_tokens([assignee_token(user.name)]) + expect_filtered_search_input_empty + end + + it 'shows created issues' do + find('.dropdown-menu').click_link("Issues I've created") + + expect(page).to have_selector('.filtered-search') + expect_tokens([author_token(user.name)]) + expect_filtered_search_input_empty + end + end + + context 'when clicking merge requests' do + let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignee: user) } + + it 'shows assigned merge requests' do + find('.dropdown-menu').click_link('Merge requests assigned to me') + + expect(page).to have_selector('.merge-requests-holder') + expect_tokens([assignee_token(user.name)]) + expect_filtered_search_input_empty + end + + it 'shows created merge requests' do + find('.dropdown-menu').click_link("Merge requests I've created") + + expect(page).to have_selector('.merge-requests-holder') + expect_tokens([author_token(user.name)]) + expect_filtered_search_input_empty + end + end + end + + context 'when entering text into the search field', :js do + before do + page.within('.search-input-wrap') do + fill_in('search', with: project.name[0..3]) + end + end + + it 'does not display the category search dropdown' do + expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i) + end end end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 2fecd1a3d27..4224cea4652 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -40,22 +40,22 @@ describe IssuablesHelper do end it 'returns "Open" when state is :opened' do - expect(helper.issuables_state_counter_text(:issues, :opened)) + expect(helper.issuables_state_counter_text(:issues, :opened, true)) .to eq('Open 42') end it 'returns "Closed" when state is :closed' do - expect(helper.issuables_state_counter_text(:issues, :closed)) + expect(helper.issuables_state_counter_text(:issues, :closed, true)) .to eq('Closed 42') end it 'returns "Merged" when state is :merged' do - expect(helper.issuables_state_counter_text(:merge_requests, :merged)) + expect(helper.issuables_state_counter_text(:merge_requests, :merged, true)) .to eq('Merged 42') end it 'returns "All" when state is :all' do - expect(helper.issuables_state_counter_text(:merge_requests, :all)) + expect(helper.issuables_state_counter_text(:merge_requests, :all, true)) .to eq('All 42') end end @@ -101,27 +101,6 @@ describe IssuablesHelper do 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 - describe '#updated_at_by' do let(:user) { create(:user) } let(:unedited_issuable) { create(:issue) } diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 40115792652..ac47f14ab46 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -98,8 +98,8 @@ import * as urlUtils from '~/lib/utils/url_utility'; assertLinks = function(list, issuesPath, mrsPath) { var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink; if (issuesPath) { - issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName; - issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName; + issuesAssignedToMeLink = issuesPath + "/?assignee_id=" + userId; + issuesIHaveCreatedLink = issuesPath + "/?author_id=" + userId; a1 = "a[href='" + issuesAssignedToMeLink + "']"; a2 = "a[href='" + issuesIHaveCreatedLink + "']"; expect(list.find(a1).length).toBe(1); @@ -107,8 +107,8 @@ import * as urlUtils from '~/lib/utils/url_utility'; expect(list.find(a2).length).toBe(1); expect(list.find(a2).text()).toBe("Issues I've created"); } - mrsAssignedToMeLink = mrsPath + "/?assignee_username=" + userName; - mrsIHaveCreatedLink = mrsPath + "/?author_username=" + userName; + mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId; + mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId; a3 = "a[href='" + mrsAssignedToMeLink + "']"; a4 = "a[href='" + mrsIHaveCreatedLink + "']"; expect(list.find(a3).length).toBe(1); From d343ef8bcee69b81190f52aded3f0ae217ffdc94 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Tue, 3 Apr 2018 20:19:51 +0200 Subject: [PATCH 025/146] Resolve conflicts in issues_filter_spec.rb --- spec/features/dashboard/issues_filter_spec.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index b15f3afca4a..bab34ac9346 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -79,19 +79,13 @@ feature 'Dashboard Issues filtering', :js do end context 'sorting' do -<<<<<<< HEAD - it 'shows sorted issues' do - sort_by('Created date') - visit_issues -======= before do visit_issues(assignee_id: user.id) end it 'remembers last sorting value' do - sorting_by('Created date') + sort_by('Created date') visit_issues(assignee_id: user.id) ->>>>>>> Fixed dashboard filtering tests expect(find('.issues-filters')).to have_content('Created date') end From d7f81b7e5def5b241c9bf7ba35a8b6c0f55a686e Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 4 Apr 2018 15:39:17 +0200 Subject: [PATCH 026/146] Increase top margin of illustration --- app/views/shared/dashboard/_no_filter_selected.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/dashboard/_no_filter_selected.html.haml b/app/views/shared/dashboard/_no_filter_selected.html.haml index e5c2f5b2346..b2e6967f6aa 100644 --- a/app/views/shared/dashboard/_no_filter_selected.html.haml +++ b/app/views/shared/dashboard/_no_filter_selected.html.haml @@ -1,6 +1,6 @@ .row.empty-state.text-center .col-xs-12 - .svg-130 + .svg-130.prepend-top-default = image_tag 'illustrations/issue-dashboard_results-without-filter.svg' .col-xs-12 .text-content From 75f8a45f6a202aa5bec00613dcd16573fc1cc46a Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 4 Apr 2018 19:28:53 +0100 Subject: [PATCH 027/146] Scrolls to the top of the page before erase click --- spec/features/projects/jobs/user_browses_job_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 8fa902cc386..a728e6eb996 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe 'User browses a job', :js do let(:user) { create(:user) } + let(:user_access_level) { :developer } let(:project) { create(:project, :repository, namespace: user.namespace) } let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') } let!(:build) { create(:ci_build, :success, :trace_artifact, :coverage, pipeline: pipeline) } @@ -19,7 +20,9 @@ describe 'User browses a job', :js do expect(page).to have_content("Job ##{build.id}") expect(page).to have_css('#build-trace') - accept_confirm { click_link('Erase') } + # scroll to the top of the page first + execute_script "window.scrollTo(0,0)" + accept_confirm { find('.js-erase-link').click } expect(page).to have_no_css('.artifacts') expect(build).not_to have_trace From d883fe1cce01f53a3acc442e8b94ca20540e0525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 4 Apr 2018 21:00:32 +0200 Subject: [PATCH 028/146] Add success CI Build empty state status --- lib/gitlab/ci/status/build/factory.rb | 3 +- lib/gitlab/ci/status/build/success.rb | 21 ++++++++++++ .../gitlab/ci/status/build/factory_spec.rb | 16 +++++++++ .../gitlab/ci/status/build/success_spec.rb | 33 +++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 lib/gitlab/ci/status/build/success.rb create mode 100644 spec/lib/gitlab/ci/status/build/success_spec.rb diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index b809a9cf766..d3a34384f6f 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -8,7 +8,8 @@ module Gitlab Status::Build::Canceled, Status::Build::Created, Status::Build::Pending, - Status::Build::Skipped], + Status::Build::Skipped, + Status::Build::Success], [Status::Build::Cancelable, Status::Build::Retryable], [Status::Build::FailedAllowed, diff --git a/lib/gitlab/ci/status/build/success.rb b/lib/gitlab/ci/status/build/success.rb new file mode 100644 index 00000000000..bafc1b2f93a --- /dev/null +++ b/lib/gitlab/ci/status/build/success.rb @@ -0,0 +1,21 @@ +module Gitlab + module Ci + module Status + module Build + class Success < Status::Extended + def illustration + { + image: 'illustrations/skipped-job_empty.svg', + size: 'svg-430', + title: _('Job has been erased') + } + end + + def self.matches?(build, user) + build.success? + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index a68fed0a41c..28166d08c02 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -36,6 +36,22 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status).to have_details expect(status).to have_action end + + context 'when job log gets erased' do + before do + build.trace.set(nil) + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Success, + Gitlab::Ci::Status::Build::Retryable] + end + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + end end context 'when build is failed' do diff --git a/spec/lib/gitlab/ci/status/build/success_spec.rb b/spec/lib/gitlab/ci/status/build/success_spec.rb new file mode 100644 index 00000000000..730cd7aefbc --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/success_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Success do + let(:user) { create(:user) } + + subject do + described_class.new(double('subject')) + end + + describe '#illustration' do + it { expect(subject.illustration).to include(:image, :size, :title) } + end + + describe '.matches?' do + subject {described_class.matches?(build, user) } + + context 'when build succeeded' do + let(:build) { create(:ci_build, :success) } + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build did not succeed' do + let(:build) { create(:ci_build, :skipped) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end From 00b45348b9dca5c394fa64a7f4975232dc712b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 4 Apr 2018 22:15:18 +0200 Subject: [PATCH 029/146] Fix job setup in success empty state specs --- lib/gitlab/ci/status/build/success.rb | 2 +- spec/lib/gitlab/ci/status/build/factory_spec.rb | 4 ++-- spec/lib/gitlab/ci/status/build/success_spec.rb | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/status/build/success.rb b/lib/gitlab/ci/status/build/success.rb index bafc1b2f93a..daf43315c90 100644 --- a/lib/gitlab/ci/status/build/success.rb +++ b/lib/gitlab/ci/status/build/success.rb @@ -12,7 +12,7 @@ module Gitlab end def self.matches?(build, user) - build.success? + !build.has_trace? && build.success? end end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 28166d08c02..d68d8f6f105 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Build::Factory do end context 'when build is successful' do - let(:build) { create(:ci_build, :success) } + let(:build) { create(:ci_build, :success, :trace_artifact) } it 'matches correct core status' do expect(factory.core_status).to be_a Gitlab::Ci::Status::Success @@ -39,7 +39,7 @@ describe Gitlab::Ci::Status::Build::Factory do context 'when job log gets erased' do before do - build.trace.set(nil) + build.erase end it 'matches correct extended statuses' do diff --git a/spec/lib/gitlab/ci/status/build/success_spec.rb b/spec/lib/gitlab/ci/status/build/success_spec.rb index 730cd7aefbc..e67ab461463 100644 --- a/spec/lib/gitlab/ci/status/build/success_spec.rb +++ b/spec/lib/gitlab/ci/status/build/success_spec.rb @@ -12,18 +12,20 @@ describe Gitlab::Ci::Status::Build::Success do end describe '.matches?' do - subject {described_class.matches?(build, user) } + subject { described_class.matches?(build, user) } - context 'when build succeeded' do + context 'when build succeeded but does not have trace' do let(:build) { create(:ci_build, :success) } it 'is a correct match' do + build.erase + expect(subject).to be true end end - context 'when build did not succeed' do - let(:build) { create(:ci_build, :skipped) } + context 'when build succeed but has trace' do + let!(:build) { create(:ci_build, :success, :trace_artifact) } it 'does not match' do expect(subject).to be false From 0b1b9c409d3adbf4517351dbb9037ed053f73e67 Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Mon, 19 Feb 2018 15:41:04 -0500 Subject: [PATCH 030/146] Add option to suppress archive commit sha Repository archives are always named `--` even if the ref is a commit. A consequence of always including the sha even for tags is that packaging a release is more difficult because both the ref and sha must be known by the packager. - add append_sha option (defaults true) to provide a method for toggling this feature. Support added to GitLab Workhorse by gitlab-org/gitlab-workhorse!232 --- app/helpers/workhorse_helper.rb | 4 ++-- lib/api/helpers.rb | 4 ++-- lib/api/repositories.rb | 2 +- lib/api/v3/repositories.rb | 2 +- lib/gitlab/git/repository.rb | 15 +++++++++++---- lib/gitlab/workhorse.rb | 4 ++-- spec/lib/gitlab/git/repository_spec.rb | 10 ++++++++-- 7 files changed, 27 insertions(+), 14 deletions(-) diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index 88f374be1e5..9f78b80c71d 100644 --- a/app/helpers/workhorse_helper.rb +++ b/app/helpers/workhorse_helper.rb @@ -24,8 +24,8 @@ module WorkhorseHelper end # Archive a Git repository and send it through Workhorse - def send_git_archive(repository, ref:, format:) - headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format)) + def send_git_archive(repository, **kwargs) + headers.store(*Gitlab::Workhorse.send_git_archive(repository, **kwargs)) head :ok end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index e59e8a45908..e22d32e4c1b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -489,8 +489,8 @@ module API header(*Gitlab::Workhorse.send_git_blob(repository, blob)) end - def send_git_archive(repository, ref:, format:) - header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format)) + def send_git_archive(repository, **kwargs) + header(*Gitlab::Workhorse.send_git_archive(repository, **kwargs)) end def send_artifacts_entry(build, entry) diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 9638c53a1df..2396dc73f0e 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -88,7 +88,7 @@ module API end get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do begin - send_git_archive user_project.repository, ref: params[:sha], format: params[:format] + send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true rescue not_found!('File') end diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb index 5b54734bb45..f701d64e886 100644 --- a/lib/api/v3/repositories.rb +++ b/lib/api/v3/repositories.rb @@ -75,7 +75,7 @@ module API end get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do begin - send_git_archive user_project.repository, ref: params[:sha], format: params[:format] + send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true rescue not_found!('File') end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index d16a096ffb9..a928a05f03f 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -394,17 +394,24 @@ module Gitlab nil end - def archive_prefix(ref, sha) + def archive_prefix(ref, sha, append_sha:) + append_sha = (ref != sha) if append_sha.nil? + project_name = self.name.chomp('.git') - "#{project_name}-#{ref.tr('/', '-')}-#{sha}" + formatted_ref = ref.tr('/', '-') + + prefix_segments = [project_name, formatted_ref] + prefix_segments << sha if append_sha + + prefix_segments.join('-') end - def archive_metadata(ref, storage_path, format = "tar.gz") + def archive_metadata(ref, storage_path, format = "tar.gz", append_sha: true) ref ||= root_ref commit = Gitlab::Git::Commit.find(self, ref) return {} if commit.nil? - prefix = archive_prefix(ref, commit.id) + prefix = archive_prefix(ref, commit.id, append_sha: append_sha) { 'RepoPath' => path, diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index b102812ec12..bdb20060cc0 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -63,10 +63,10 @@ module Gitlab ] end - def send_git_archive(repository, ref:, format:) + def send_git_archive(repository, ref:, format:, append_sha: true) format ||= 'tar.gz' format.downcase! - params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format) + params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha) raise "Repository or ref not found" if params.empty? if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 5cbe2808d0b..2bb8aa69def 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -247,16 +247,22 @@ describe Gitlab::Git::Repository, seed_helper: true do end it 'returns parameterised string for a ref containing slashes' do - prefix = repository.archive_prefix('test/branch', 'SHA') + prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: true) expect(prefix).to eq("#{project_name}-test-branch-SHA") end it 'returns correct string for a ref containing dots' do - prefix = repository.archive_prefix('test.branch', 'SHA') + prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: true) expect(prefix).to eq("#{project_name}-test.branch-SHA") end + + it 'returns string with sha when append_sha is false' do + prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: false) + + expect(prefix).to eq("#{project_name}-test.branch") + end end describe '#archive' do From 10143937c1fdfe2ef4628f32a1e87ca564233d17 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 13:54:41 +0530 Subject: [PATCH 031/146] Add page helper methods for notesApp --- app/assets/javascripts/lib/utils/dom_utils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js index de65ea15a60..914de9de940 100644 --- a/app/assets/javascripts/lib/utils/dom_utils.js +++ b/app/assets/javascripts/lib/utils/dom_utils.js @@ -1,7 +1,12 @@ -/* eslint-disable import/prefer-default-export */ +import $ from 'jquery'; +import { isInIssuePage, isInMRPage, isInEpicPage, hasVueMRDiscussionsCookie } from './common_utils'; + +const isVueMRDiscussions = () => isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible'); export const addClassIfElementExists = (element, className) => { if (element) { element.classList.add(className); } }; + +export const isInVueNoteablePage = () => isInIssuePage() || isInEpicPage() || isVueMRDiscussions(); From 1f27c6382510c91a129715642f146531b1074544 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 13:54:59 +0530 Subject: [PATCH 032/146] Use `isInVueNoteablePage` from dom_utils --- app/assets/javascripts/awards_handler.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 0e1ca7fe883..976d32abe9b 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -4,7 +4,8 @@ import $ from 'jquery'; import _ from 'underscore'; import Cookies from 'js-cookie'; import { __ } from './locale'; -import { isInIssuePage, isInMRPage, isInEpicPage, hasVueMRDiscussionsCookie, updateTooltipTitle } from './lib/utils/common_utils'; +import { updateTooltipTitle } from './lib/utils/common_utils'; +import { isInVueNoteablePage } from './lib/utils/dom_utils'; import flash from './flash'; import axios from './lib/utils/axios_utils'; @@ -243,7 +244,7 @@ class AwardsHandler { addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { const isMainAwardsBlock = votesBlock.closest('.js-noteable-awards').length; - if (this.isInVueNoteablePage() && !isMainAwardsBlock) { + if (isInVueNoteablePage() && !isMainAwardsBlock) { const id = votesBlock.attr('id').replace('note_', ''); this.hideMenuElement($('.emoji-menu')); @@ -295,16 +296,8 @@ class AwardsHandler { } } - isVueMRDiscussions() { - return isInMRPage() && hasVueMRDiscussionsCookie() && !$('#diffs').is(':visible'); - } - - isInVueNoteablePage() { - return isInIssuePage() || isInEpicPage() || this.isVueMRDiscussions(); - } - getVotesBlock() { - if (this.isInVueNoteablePage()) { + if (isInVueNoteablePage()) { const $el = $('.js-add-award.is-active').closest('.note.timeline-entry'); if ($el.length) { From 1db4ea20c6ce67559a5c28b6b423abe292354dd8 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 13:55:34 +0530 Subject: [PATCH 033/146] Set noteableType on noteableData as provided from DOM dataset --- app/assets/javascripts/mr_notes/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js index 096c4ef5f31..e3c5bf06b3d 100644 --- a/app/assets/javascripts/mr_notes/index.js +++ b/app/assets/javascripts/mr_notes/index.js @@ -13,8 +13,11 @@ export default function initMrNotes() { data() { const notesDataset = document.getElementById('js-vue-mr-discussions') .dataset; + const noteableData = JSON.parse(notesDataset.noteableData); + noteableData.noteableType = notesDataset.noteableType; + return { - noteableData: JSON.parse(notesDataset.noteableData), + noteableData, currentUserData: JSON.parse(notesDataset.currentUserData), notesData: JSON.parse(notesDataset.notesData), }; From 02968711128d4bd8939467181504cbe314274cc2 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 5 Apr 2018 10:19:55 +0200 Subject: [PATCH 034/146] Use template strings in search_autocomplete_spec.js --- spec/javascripts/search_autocomplete_spec.js | 216 ++++++++++--------- 1 file changed, 111 insertions(+), 105 deletions(-) diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index ac47f14ab46..1a27955983d 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -6,8 +6,21 @@ import SearchAutocomplete from '~/search_autocomplete'; import '~/lib/utils/common_utils'; import * as urlUtils from '~/lib/utils/url_utility'; -(function() { - var assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget; +describe('Search autocomplete dropdown', () => { + var assertLinks, + dashboardIssuesPath, + dashboardMRsPath, + groupIssuesPath, + groupMRsPath, + groupName, + mockDashboardOptions, + mockGroupOptions, + mockProjectOptions, + projectIssuesPath, + projectMRsPath, + projectName, + userId, + widget; var userName = 'root'; widget = null; @@ -66,133 +79,126 @@ import * as urlUtils from '~/lib/utils/url_utility'; // Mock `gl` object in window for dashboard specific page. App code will need it. mockDashboardOptions = function() { window.gl || (window.gl = {}); - return window.gl.dashboardOptions = { + return (window.gl.dashboardOptions = { issuesPath: dashboardIssuesPath, - mrPath: dashboardMRsPath - }; + mrPath: dashboardMRsPath, + }); }; // Mock `gl` object in window for project specific page. App code will need it. mockProjectOptions = function() { window.gl || (window.gl = {}); - return window.gl.projectOptions = { + return (window.gl.projectOptions = { 'gitlab-ce': { issuesPath: projectIssuesPath, mrPath: projectMRsPath, - projectName: projectName - } - }; + projectName: projectName, + }, + }); }; mockGroupOptions = function() { window.gl || (window.gl = {}); - return window.gl.groupOptions = { + return (window.gl.groupOptions = { 'gitlab-org': { issuesPath: groupIssuesPath, mrPath: groupMRsPath, - projectName: groupName - } - }; + projectName: groupName, + }, + }); }; assertLinks = function(list, issuesPath, mrsPath) { - var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink; if (issuesPath) { - issuesAssignedToMeLink = issuesPath + "/?assignee_id=" + userId; - issuesIHaveCreatedLink = issuesPath + "/?author_id=" + userId; - a1 = "a[href='" + issuesAssignedToMeLink + "']"; - a2 = "a[href='" + issuesIHaveCreatedLink + "']"; - expect(list.find(a1).length).toBe(1); - expect(list.find(a1).text()).toBe('Issues assigned to me'); - expect(list.find(a2).length).toBe(1); - expect(list.find(a2).text()).toBe("Issues I've created"); + const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_id=${userId}"]`; + const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_id=${userId}"]`; + expect(list.find(issuesAssignedToMeLink).length).toBe(1); + expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me'); + expect(list.find(issuesIHaveCreatedLink).length).toBe(1); + expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created"); } - mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId; - mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId; - a3 = "a[href='" + mrsAssignedToMeLink + "']"; - a4 = "a[href='" + mrsIHaveCreatedLink + "']"; - expect(list.find(a3).length).toBe(1); - expect(list.find(a3).text()).toBe('Merge requests assigned to me'); - expect(list.find(a4).length).toBe(1); - return expect(list.find(a4).text()).toBe("Merge requests I've created"); + const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_id=${userId}"]`; + const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_id=${userId}"]`; + expect(list.find(mrsAssignedToMeLink).length).toBe(1); + expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me'); + expect(list.find(mrsIHaveCreatedLink).length).toBe(1); + expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created"); }; - describe('Search autocomplete dropdown', function() { - preloadFixtures('static/search_autocomplete.html.raw'); - beforeEach(function() { - loadFixtures('static/search_autocomplete.html.raw'); + preloadFixtures('static/search_autocomplete.html.raw'); + beforeEach(function() { + loadFixtures('static/search_autocomplete.html.raw'); - // Prevent turbolinks from triggering within gl_dropdown - spyOn(urlUtils, 'visitUrl').and.returnValue(true); + // Prevent turbolinks from triggering within gl_dropdown + spyOn(urlUtils, 'visitUrl').and.returnValue(true); - window.gon = {}; - window.gon.current_user_id = userId; - window.gon.current_username = userName; + window.gon = {}; + window.gon.current_user_id = userId; + window.gon.current_username = userName; - return widget = new SearchAutocomplete(); - }); - - afterEach(function() { - // Undo what we did to the shared - removeBodyAttributes(); - window.gon = {}; - }); - it('should show Dashboard specific dropdown menu', function() { - var list; - addBodyAttributes(); - mockDashboardOptions(); - widget.searchInput.triggerHandler('focus'); - list = widget.wrap.find('.dropdown-menu').find('ul'); - return assertLinks(list, dashboardIssuesPath, dashboardMRsPath); - }); - it('should show Group specific dropdown menu', function() { - var list; - addBodyAttributes('group'); - mockGroupOptions(); - widget.searchInput.triggerHandler('focus'); - list = widget.wrap.find('.dropdown-menu').find('ul'); - return assertLinks(list, groupIssuesPath, groupMRsPath); - }); - it('should show Project specific dropdown menu', function() { - var list; - addBodyAttributes('project'); - mockProjectOptions(); - widget.searchInput.triggerHandler('focus'); - list = widget.wrap.find('.dropdown-menu').find('ul'); - return assertLinks(list, projectIssuesPath, projectMRsPath); - }); - it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() { - addBodyAttributes('project'); - disableProjectIssues(); - mockProjectOptions(); - widget.searchInput.triggerHandler('focus'); - const list = widget.wrap.find('.dropdown-menu').find('ul'); - assertLinks(list, null, projectMRsPath); - }); - it('should not show category related menu if there is text in the input', function() { - var link, list; - addBodyAttributes('project'); - mockProjectOptions(); - widget.searchInput.val('help'); - widget.searchInput.triggerHandler('focus'); - list = widget.wrap.find('.dropdown-menu').find('ul'); - link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']"; - return expect(list.find(link).length).toBe(0); - }); - return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() { - var ENTER = 13; - var DOWN = 40; - addBodyAttributes(); - mockDashboardOptions(true); - var submitSpy = spyOnEvent('form', 'submit'); - widget.searchInput.triggerHandler('focus'); - widget.wrap.trigger($.Event('keydown', { which: DOWN })); - var enterKeyEvent = $.Event('keydown', { which: ENTER }); - widget.searchInput.trigger(enterKeyEvent); - // This does not currently catch failing behavior. For security reasons, - // browsers will not trigger default behavior (form submit, in this - // example) on JavaScript-created keypresses. - expect(submitSpy).not.toHaveBeenTriggered(); - }); + return (widget = new SearchAutocomplete()); }); -}).call(window); + + afterEach(function() { + // Undo what we did to the shared + removeBodyAttributes(); + window.gon = {}; + }); + it('should show Dashboard specific dropdown menu', function() { + var list; + addBodyAttributes(); + mockDashboardOptions(); + widget.searchInput.triggerHandler('focus'); + list = widget.wrap.find('.dropdown-menu').find('ul'); + return assertLinks(list, dashboardIssuesPath, dashboardMRsPath); + }); + it('should show Group specific dropdown menu', function() { + var list; + addBodyAttributes('group'); + mockGroupOptions(); + widget.searchInput.triggerHandler('focus'); + list = widget.wrap.find('.dropdown-menu').find('ul'); + return assertLinks(list, groupIssuesPath, groupMRsPath); + }); + it('should show Project specific dropdown menu', function() { + var list; + addBodyAttributes('project'); + mockProjectOptions(); + widget.searchInput.triggerHandler('focus'); + list = widget.wrap.find('.dropdown-menu').find('ul'); + return assertLinks(list, projectIssuesPath, projectMRsPath); + }); + it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() { + addBodyAttributes('project'); + disableProjectIssues(); + mockProjectOptions(); + widget.searchInput.triggerHandler('focus'); + const list = widget.wrap.find('.dropdown-menu').find('ul'); + assertLinks(list, null, projectMRsPath); + }); + it('should not show category related menu if there is text in the input', function() { + var link, list; + addBodyAttributes('project'); + mockProjectOptions(); + widget.searchInput.val('help'); + widget.searchInput.triggerHandler('focus'); + list = widget.wrap.find('.dropdown-menu').find('ul'); + link = "a[href='" + projectIssuesPath + '/?assignee_id=' + userId + "']"; + return expect(list.find(link).length).toBe(0); + }); + it('should not submit the search form when selecting an autocomplete row with the keyboard', function() { + var ENTER = 13; + var DOWN = 40; + addBodyAttributes(); + mockDashboardOptions(true); + var submitSpy = spyOnEvent('form', 'submit'); + widget.searchInput.triggerHandler('focus'); + widget.wrap.trigger($.Event('keydown', { which: DOWN })); + var enterKeyEvent = $.Event('keydown', { which: ENTER }); + widget.searchInput.trigger(enterKeyEvent); + // This does not currently catch failing behavior. For security reasons, + // browsers will not trigger default behavior (form submit, in this + // example) on JavaScript-created keypresses. + expect(submitSpy).not.toHaveBeenTriggered(); + }); +}); From 323bac4a6e1de5d9ba9c1cb3a2868f514888c44a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 5 Apr 2018 11:30:02 +0200 Subject: [PATCH 035/146] Improve docs about pipeline variables expressions --- doc/ci/variables/README.md | 55 ++++++++++++++++++++++++++++++-------- doc/ci/yaml/README.md | 3 ++- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 9f268f47e6f..381405a9db9 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -454,8 +454,8 @@ export CI_REGISTRY_PASSWORD="longalfanumstring" > Variables expressions were added in GitLab 10.7. It is possible to use variables expressions with only / except policies in -`.gitlab-ci.yml`. By using this approach you can limit what builds are going to -be created within a pipeline after pushing code to GitLab. +`.gitlab-ci.yml`. By using this approach you can limit what jobs are going to +be created within a pipeline after pushing a code to GitLab. This is particularly useful in combination with secret variables and triggered pipeline variables. @@ -470,22 +470,21 @@ deploy: - $STAGING ``` -Each provided variables expression is going to be evaluated before creating -a pipeline. +Each expression provided is going to be evaluated before creating a pipeline. If any of the conditions in `variables` evaluates to truth when using `only`, a new job is going to be created. If any of the expressions evaluates to truth when `except` is being used, a job is not going to be created. -This follows usual rules for `only` / `except` policies. +This follows usual rules for [`only` / `except` policies][builds-policies]. ### Supported syntax -Below you can find currently supported syntax reference: +Below you can find supported syntax reference: 1. Equality matching using a string - Example: `$VARIABLE == "some value"` + > Example: `$VARIABLE == "some value"` You can use equality operator `==` to compare a variable content to a string. We support both, double quotes and single quotes to define a string @@ -494,26 +493,59 @@ Below you can find currently supported syntax reference: 1. Checking for an undefined value - It sometimes happens that you want to check whether variable is defined or - not. To do that, you can compare variable to `null` value, like + > Example: `$VARIABLE == null` + + It sometimes happens that you want to check whether a variable is defined + or not. To do that, you can compare a variable to `null` keyword, like `$VARIABLE == null`. This expression is going to evaluate to truth if - variable is not set. + variable is not defined. 1. Checking for an empty variable + > Example: `$VARIABLE == ""` + If you want to check whether a variable is defined, but is empty, you can simply compare it against an empty string, like `$VAR == ''`. 1. Comparing two variables - It is possible to compare two variables. `$VARIABLE_1 == $VARIABLE_2`. + > Example: `$VARIABLE_1 == $VARIABLE_2` + + It is possible to compare two variables. This is going to compare values + of these variables. 1. Variable presence check + > Example: `$STAGING` + If you only want to create a job when there is some variable present, which means that it is defined and non-empty, you can simply use variable name as an expression, like `$STAGING`. If `$STAGING` variable is defined, and is non empty, expression will evaluate to truth. + `$STAGING` value needs to a string, with length higher than zero. + Variable that contains only whitespace characters is not an empty variable. + +### Unsupported predefined variables + +Because GitLab evaluates variables before creating jobs, we do not support a +few variables that depend on persistence layer, like `$CI_JOB_ID`. + +Environments (like `production` or `staging`) are also being created based on +what jobs pipeline consists of, thus some environment-specific variables are +not supported as well. + +We do not support variables containing tokens because of security reasons. + +You can find a full list of unsupported variables below: + +- `CI_JOB_ID` +- `CI_JOB_TOKEN` +- `CI_BUILD_ID` +- `CI_BUILD_TOKEN` +- `CI_REGISTRY_USER` +- `CI_REGISTRY_PASSWORD` +- `CI_REPOSITORY_URL` +- `CI_ENVIRONMENT_URL` [ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI secret variables" [eep]: https://about.gitlab.com/products/ "Available only in GitLab Premium" @@ -525,3 +557,4 @@ Below you can find currently supported syntax reference: [triggered]: ../triggers/README.md [triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger [subgroups]: ../../user/group/subgroups/index.md +[builds-policies]: ../yaml/README.md#only-and-except-complex diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index be114e7008e..68aa64b3834 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -354,7 +354,7 @@ deploy: - $STAGING ``` -Learn more about variables expressions on a separate page. +Learn more about variables expressions on [a separate page][variables-expressions]. ## `tags` @@ -1574,3 +1574,4 @@ CI with various languages. [ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447 [ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909 [schedules]: ../../user/project/pipelines/schedules.md +[variables-expressions]: ../variables/README.md#variables-expressions From 9b1677b2deeec1faf0dd1d60a2b0c47e80b58433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 5 Apr 2018 12:01:31 +0200 Subject: [PATCH 036/146] Remove artifacts_file/metadata from project export --- lib/gitlab/import_export/import_export.yml | 2 + spec/lib/gitlab/import_export/project.json | 60 ---------------------- 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 4bdd01f5e94..8a0cddff579 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -124,6 +124,8 @@ excluded_attributes: - :trace - :token - :when + - :artifacts_file + - :artifacts_metadata push_event_payload: - :event_id project_badges: diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 4a51777ba9b..08cee7825cd 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6181,12 +6181,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": null - }, - "artifacts_metadata": { - "url": null - }, "erased_by_id": null, "erased_at": null, "type": "Ci::Build", @@ -6219,12 +6213,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts.zip" - }, - "artifacts_metadata": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/72/p5_build_artifacts_metadata.gz" - }, "erased_by_id": null, "erased_at": null } @@ -6293,12 +6281,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts.zip" - }, - "artifacts_metadata": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/74/p5_build_artifacts_metadata.gz" - }, "erased_by_id": null, "erased_at": null }, @@ -6328,12 +6310,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": null - }, - "artifacts_metadata": { - "url": null - }, "erased_by_id": null, "erased_at": null } @@ -6393,12 +6369,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts.zip" - }, - "artifacts_metadata": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/76/p5_build_artifacts_metadata.gz" - }, "erased_by_id": null, "erased_at": null }, @@ -6428,12 +6398,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts.zip" - }, - "artifacts_metadata": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/75/p5_build_artifacts_metadata.gz" - }, "erased_by_id": null, "erased_at": null } @@ -6493,12 +6457,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts.zip" - }, - "artifacts_metadata": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/78/p5_build_artifacts_metadata.gz" - }, "erased_by_id": null, "erased_at": null }, @@ -6528,12 +6486,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts.zip" - }, - "artifacts_metadata": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/77/p5_build_artifacts_metadata.gz" - }, "erased_by_id": null, "erased_at": null } @@ -6593,12 +6545,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": null - }, - "artifacts_metadata": { - "url": null - }, "erased_by_id": null, "erased_at": null }, @@ -6628,12 +6574,6 @@ "user_id": null, "target_url": null, "description": null, - "artifacts_file": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts.zip" - }, - "artifacts_metadata": { - "url": "/Users/Test/Test/gitlab-development-kit/gitlab/shared/artifacts/2016_03/5/80/p5_build_artifacts_metadata.gz" - }, "erased_by_id": null, "erased_at": null } From f379e1b370d033097c1d0102401eb6156384890c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 5 Apr 2018 13:52:54 +0200 Subject: [PATCH 037/146] Rename success to erased empty state spec --- .../ci/status/build/{success.rb => erased.rb} | 4 +-- lib/gitlab/ci/status/build/factory.rb | 2 +- .../build/{success_spec.rb => erased_spec.rb} | 12 +++---- .../gitlab/ci/status/build/factory_spec.rb | 35 ++++++++++++------- 4 files changed, 31 insertions(+), 22 deletions(-) rename lib/gitlab/ci/status/build/{success.rb => erased.rb} (80%) rename spec/lib/gitlab/ci/status/build/{success_spec.rb => erased_spec.rb} (62%) diff --git a/lib/gitlab/ci/status/build/success.rb b/lib/gitlab/ci/status/build/erased.rb similarity index 80% rename from lib/gitlab/ci/status/build/success.rb rename to lib/gitlab/ci/status/build/erased.rb index daf43315c90..3a5113b16b6 100644 --- a/lib/gitlab/ci/status/build/success.rb +++ b/lib/gitlab/ci/status/build/erased.rb @@ -2,7 +2,7 @@ module Gitlab module Ci module Status module Build - class Success < Status::Extended + class Erased < Status::Extended def illustration { image: 'illustrations/skipped-job_empty.svg', @@ -12,7 +12,7 @@ module Gitlab end def self.matches?(build, user) - !build.has_trace? && build.success? + build.erased? end end end diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index d3a34384f6f..466a0a989ad 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -9,7 +9,7 @@ module Gitlab Status::Build::Created, Status::Build::Pending, Status::Build::Skipped, - Status::Build::Success], + Status::Build::Erased], [Status::Build::Cancelable, Status::Build::Retryable], [Status::Build::FailedAllowed, diff --git a/spec/lib/gitlab/ci/status/build/success_spec.rb b/spec/lib/gitlab/ci/status/build/erased_spec.rb similarity index 62% rename from spec/lib/gitlab/ci/status/build/success_spec.rb rename to spec/lib/gitlab/ci/status/build/erased_spec.rb index e67ab461463..0acd271e375 100644 --- a/spec/lib/gitlab/ci/status/build/success_spec.rb +++ b/spec/lib/gitlab/ci/status/build/erased_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Ci::Status::Build::Success do +describe Gitlab::Ci::Status::Build::Erased do let(:user) { create(:user) } subject do @@ -14,18 +14,16 @@ describe Gitlab::Ci::Status::Build::Success do describe '.matches?' do subject { described_class.matches?(build, user) } - context 'when build succeeded but does not have trace' do - let(:build) { create(:ci_build, :success) } + context 'when build is erased' do + let(:build) { create(:ci_build, :success, :erased) } it 'is a correct match' do - build.erase - expect(subject).to be true end end - context 'when build succeed but has trace' do - let!(:build) { create(:ci_build, :success, :trace_artifact) } + context 'when build is not erased' do + let(:build) { create(:ci_build, :success, :trace_artifact) } it 'does not match' do expect(subject).to be false diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index d68d8f6f105..94eedc50bb2 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -36,21 +36,32 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status).to have_details expect(status).to have_action end + end - context 'when job log gets erased' do - before do - build.erase - end + context 'when build is erased' do + let(:build) { create(:ci_build, :success, :erased) } - it 'matches correct extended statuses' do - expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Success, - Gitlab::Ci::Status::Build::Retryable] - end + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Success + end - it 'fabricates a retryable build status' do - expect(status).to be_a Gitlab::Ci::Status::Build::Retryable - end + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Erased, + Gitlab::Ci::Status::Build::Retryable] + end + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'passed' + expect(status.icon).to eq 'status_success' + expect(status.favicon).to eq 'favicon_status_success' + expect(status.label).to eq 'passed' + expect(status).to have_details + expect(status).to have_action end end From 678620cce67cc283b19b75137f747f9415aaf942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Tue, 3 Apr 2018 18:47:33 +0200 Subject: [PATCH 038/146] Add `direct_upload` setting for artifacts --- .../projects/lfs_storage_controller.rb | 13 +- app/models/ci/job_artifact.rb | 5 + app/uploaders/job_artifact_uploader.rb | 4 + app/uploaders/legacy_artifact_uploader.rb | 4 + app/uploaders/object_storage.rb | 56 +++---- config/initializers/1_settings.rb | 1 + .../artifacts_direct_upload_support.rb | 7 + doc/administration/job_artifacts.md | 1 + lib/api/api.rb | 8 + lib/api/helpers.rb | 22 --- lib/api/runner.rb | 36 ++-- lib/gitlab/middleware/multipart.rb | 2 +- lib/gitlab/workhorse.rb | 4 - lib/uploaded_file.rb | 40 ++++- .../artifacts_direct_upload_support_spec.rb | 71 ++++++++ spec/lib/uploaded_file_spec.rb | 116 +++++++++++++ spec/requests/api/runner_spec.rb | 119 ++++++++++--- spec/requests/lfs_http_spec.rb | 19 ++- spec/support/stub_configuration.rb | 4 + spec/uploaders/object_storage_spec.rb | 157 ++++++------------ 20 files changed, 467 insertions(+), 222 deletions(-) create mode 100644 config/initializers/artifacts_direct_upload_support.rb create mode 100644 spec/initializers/artifacts_direct_upload_support_spec.rb create mode 100644 spec/lib/uploaded_file_spec.rb diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb index 2515e4b9a17..ebde0df1f7b 100644 --- a/app/controllers/projects/lfs_storage_controller.rb +++ b/app/controllers/projects/lfs_storage_controller.rb @@ -31,7 +31,9 @@ class Projects::LfsStorageController < Projects::GitHttpClientController render plain: 'Unprocessable entity', status: 422 end rescue ActiveRecord::RecordInvalid - render_400 + render_lfs_forbidden + rescue UploadedFile::InvalidPathError + render_lfs_forbidden rescue ObjectStorage::RemoteStoreError render_lfs_forbidden end @@ -66,10 +68,11 @@ class Projects::LfsStorageController < Projects::GitHttpClientController end def create_file!(oid, size) - LfsObject.new(oid: oid, size: size).tap do |object| - object.file.store_workhorse_file!(params, :file) - object.save! - end + uploaded_file = UploadedFile.from_params( + params, :file, LfsObjectUploader.workhorse_local_upload_path) + return unless uploaded_file + + LfsObject.create!(oid: oid, size: size, file: uploaded_file) end def link_to_project!(object) diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index df57b4f65e3..fbb95fe16df 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -7,6 +7,7 @@ module Ci belongs_to :project belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id + before_save :update_file_store before_save :set_size, if: :file_changed? scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) } @@ -21,6 +22,10 @@ module Ci trace: 3 } + def update_file_store + self.file_store = file.object_store + end + def self.artifacts_size_for(project) self.where(project: project).sum(:size) end diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index ef0f8acefd6..dd86753479d 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -2,6 +2,8 @@ class JobArtifactUploader < GitlabUploader extend Workhorse::UploadPath include ObjectStorage::Concern + ObjectNotReadyError = Class.new(StandardError) + storage_options Gitlab.config.artifacts def size @@ -25,6 +27,8 @@ class JobArtifactUploader < GitlabUploader private def dynamic_segment + raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id + creation_date = model.created_at.utc.strftime('%Y_%m_%d') File.join(disk_hash[0..1], disk_hash[2..3], disk_hash, diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb index b726b053493..efb7893d153 100644 --- a/app/uploaders/legacy_artifact_uploader.rb +++ b/app/uploaders/legacy_artifact_uploader.rb @@ -2,6 +2,8 @@ class LegacyArtifactUploader < GitlabUploader extend Workhorse::UploadPath include ObjectStorage::Concern + ObjectNotReadyError = Class.new(StandardError) + storage_options Gitlab.config.artifacts def store_dir @@ -11,6 +13,8 @@ class LegacyArtifactUploader < GitlabUploader private def dynamic_segment + raise ObjectNotReadyError, 'Build is not ready' unless model.id + File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s) end end diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb index 3f59ee39299..bd258e04d3f 100644 --- a/app/uploaders/object_storage.rb +++ b/app/uploaders/object_storage.rb @@ -156,11 +156,10 @@ module ObjectStorage end def workhorse_authorize - if options = workhorse_remote_upload_options - { RemoteObject: options } - else - { TempPath: workhorse_local_upload_path } - end + { + RemoteObject: workhorse_remote_upload_options, + TempPath: workhorse_local_upload_path + }.compact end def workhorse_local_upload_path @@ -293,16 +292,14 @@ module ObjectStorage } end - def store_workhorse_file!(params, identifier) - filename = params["#{identifier}.name"] - - if remote_object_id = params["#{identifier}.remote_id"] - store_remote_file!(remote_object_id, filename) - elsif local_path = params["#{identifier}.path"] - store_local_file!(local_path, filename) - else - raise RemoteStoreError, 'Bad file' + def cache!(new_file = sanitized_file) + # We intercept ::UploadedFile which might be stored on remote storage + # We use that for "accelerated" uploads, where we store result on remote storage + if new_file.is_a?(::UploadedFile) && new_file.remote_id + return cache_remote_file!(new_file.remote_id, new_file.original_filename) end + + super end private @@ -313,38 +310,31 @@ module ObjectStorage self.file_storage? end - def store_remote_file!(remote_object_id, filename) - raise RemoteStoreError, 'Missing filename' unless filename - + def cache_remote_file!(remote_object_id, original_filename) file_path = File.join(TMP_UPLOAD_PATH, remote_object_id) file_path = Pathname.new(file_path).cleanpath.to_s raise RemoteStoreError, 'Bad file path' unless file_path.start_with?(TMP_UPLOAD_PATH + '/') - self.object_store = Store::REMOTE - # TODO: # This should be changed to make use of `tmp/cache` mechanism # instead of using custom upload directory, # using tmp/cache makes this implementation way easier than it is today - CarrierWave::Storage::Fog::File.new(self, storage, file_path).tap do |file| + CarrierWave::Storage::Fog::File.new(self, storage_for(Store::REMOTE), file_path).tap do |file| raise RemoteStoreError, 'Missing file' unless file.exists? - self.filename = filename - self.file = storage.store!(file) + # Remote stored file, we force to store on remote storage + self.object_store = Store::REMOTE + + # TODO: + # We store file internally and force it to be considered as `cached` + # This makes CarrierWave to store file in permament location (copy/delete) + # once this object is saved, but not sooner + @cache_id = "force-to-use-cache" # rubocop:disable Gitlab/ModuleWithInstanceVariables + @file = file # rubocop:disable Gitlab/ModuleWithInstanceVariables + @filename = original_filename # rubocop:disable Gitlab/ModuleWithInstanceVariables end end - def store_local_file!(local_path, filename) - raise RemoteStoreError, 'Missing filename' unless filename - - root_path = File.realpath(self.class.workhorse_local_upload_path) - file_path = File.realpath(local_path) - raise RemoteStoreError, 'Bad file path' unless file_path.start_with?(root_path) - - self.object_store = Store::LOCAL - self.store!(UploadedFile.new(file_path, filename)) - end - # this is a hack around CarrierWave. The #migrate method needs to be # able to force the current file to the migrated file upon success. def file=(file) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 6bfbf3b7540..67ca5eae4fb 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -308,6 +308,7 @@ Settings.artifacts['max_size'] ||= 100 # in megabytes Settings.artifacts['object_store'] ||= Settingslogic.new({}) Settings.artifacts['object_store']['enabled'] = false if Settings.artifacts['object_store']['enabled'].nil? Settings.artifacts['object_store']['remote_directory'] ||= nil +Settings.artifacts['object_store']['direct_upload'] = false if Settings.artifacts['object_store']['direct_upload'].nil? Settings.artifacts['object_store']['background_upload'] = true if Settings.artifacts['object_store']['background_upload'].nil? Settings.artifacts['object_store']['proxy_download'] = false if Settings.artifacts['object_store']['proxy_download'].nil? # Convert upload connection settings to use string keys, to make Fog happy diff --git a/config/initializers/artifacts_direct_upload_support.rb b/config/initializers/artifacts_direct_upload_support.rb new file mode 100644 index 00000000000..d2bc35ea613 --- /dev/null +++ b/config/initializers/artifacts_direct_upload_support.rb @@ -0,0 +1,7 @@ +artifacts_object_store = Gitlab.config.artifacts.object_store + +if artifacts_object_store.enabled && + artifacts_object_store.direct_upload && + artifacts_object_store.connection&.provider.to_s != 'Google' + raise "Only 'Google' is supported as a object storage provider when 'direct_upload' of artifacts is used" +end diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index ac3a12930c3..896cb93e5ed 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -108,6 +108,7 @@ For source installations the following settings are nested under `artifacts:` an |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | | `remote_directory` | The bucket name where Artfacts will be stored| | +| `direct_upload` | Set to true to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. Currently only `Google` provider is supported | `false` | | `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | | `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `connection` | Various connection options described below | | diff --git a/lib/api/api.rb b/lib/api/api.rb index 62ffebeacb0..073471b4c4d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -78,6 +78,14 @@ module API rack_response({ 'message' => '404 Not found' }.to_json, 404) end + rescue_from UploadedFile::InvalidPathError do |e| + rack_response({ 'message' => e.message }.to_json, 400) + end + + rescue_from ObjectStorage::RemoteStoreError do |e| + rack_response({ 'message' => e.message }.to_json, 500) + end + # Retain 405 error rather than a 500 error for Grape 0.15.0+. # https://github.com/ruby-grape/grape/blob/a3a28f5b5dfbb2797442e006dbffd750b27f2a76/UPGRADING.md#changes-to-method-not-allowed-routes rescue_from Grape::Exceptions::MethodNotAllowed do |e| diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index e59e8a45908..65370ad5e56 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -388,28 +388,6 @@ module API # file helpers - def uploaded_file(field, uploads_path) - if params[field] - bad_request!("#{field} is not a file") unless params[field][:filename] - return params[field] - end - - return nil unless params["#{field}.path"] && params["#{field}.name"] - - # sanitize file paths - # this requires all paths to exist - required_attributes! %W(#{field}.path) - uploads_path = File.realpath(uploads_path) - file_path = File.realpath(params["#{field}.path"]) - bad_request!('Bad file path') unless file_path.start_with?(uploads_path) - - UploadedFile.new( - file_path, - params["#{field}.name"], - params["#{field}.type"] || 'application/octet-stream' - ) - end - def present_disk_file!(path, filename, content_type = 'application/octet-stream') filename ||= File.basename(path) header['Content-Disposition'] = "attachment; filename=#{filename}" diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 834253d8e94..60aeb69e10a 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -186,7 +186,7 @@ module API status 200 content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - Gitlab::Workhorse.artifact_upload_ok + JobArtifactUploader.workhorse_authorize end desc 'Upload artifacts for job' do @@ -201,14 +201,15 @@ module API requires :id, type: Integer, desc: %q(Job's ID) optional :token, type: String, desc: %q(Job's authentication token) optional :expire_in, type: String, desc: %q(Specify when artifacts should expire) - optional :file, type: File, desc: %q(Artifact's file) optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse)) optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse)) - optional 'file.sha256', type: String, desc: %q(sha256 checksum of the file) + optional 'file.size', type: Integer, desc: %q(real size of file (generated by Workhorse)) + optional 'file.sha256', type: String, desc: %q(sha256 checksum of the file (generated by Workhorse)) optional 'metadata.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'metadata.name', type: String, desc: %q(filename (generated by Workhorse)) - optional 'metadata.sha256', type: String, desc: %q(sha256 checksum of the file) + optional 'metadata.size', type: Integer, desc: %q(real size of metadata (generated by Workhorse)) + optional 'metadata.sha256', type: String, desc: %q(sha256 checksum of metadata (generated by Workhorse)) end post '/:id/artifacts' do not_allowed! unless Gitlab.config.artifacts.enabled @@ -217,21 +218,34 @@ module API job = authenticate_job! forbidden!('Job is not running!') unless job.running? - workhorse_upload_path = JobArtifactUploader.workhorse_upload_path - artifacts = uploaded_file(:file, workhorse_upload_path) - metadata = uploaded_file(:metadata, workhorse_upload_path) + artifacts = UploadedFile.from_params(params, :file, JobArtifactUploader.workhorse_local_upload_path) + metadata = UploadedFile.from_params(params, :metadata, JobArtifactUploader.workhorse_local_upload_path) bad_request!('Missing artifacts file!') unless artifacts file_to_large! unless artifacts.size < max_artifacts_size + bad_request!("Already uploaded") if job.job_artifacts_archive + expire_in = params['expire_in'] || Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in - job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, file_sha256: params['file.sha256'], expire_in: expire_in) - job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, file_sha256: params['metadata.sha256'], expire_in: expire_in) if metadata - job.artifacts_expire_in = expire_in + job.build_job_artifacts_archive( + project: job.project, + file: artifacts, + file_type: :archive, + file_sha256: artifacts.sha256, + expire_in: expire_in) - if job.save + if metadata + job.build_job_artifacts_metadata( + project: job.project, + file: metadata, + file_type: :metadata, + file_sha256: metadata.sha256, + expire_in: expire_in) + end + + if job.update(artifacts_expire_in: expire_in) present job, with: Entities::JobRequest::Response else render_validation_error!(job) diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb index d4c54049b74..a5f5d719cc1 100644 --- a/lib/gitlab/middleware/multipart.rb +++ b/lib/gitlab/middleware/multipart.rb @@ -82,7 +82,7 @@ module Gitlab end def open_file(path, name) - ::UploadedFile.new(path, name || File.basename(path), 'application/octet-stream') + ::UploadedFile.new(path, filename: name || File.basename(path), content_type: 'application/octet-stream') end end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index b102812ec12..2faeaf16d55 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -36,10 +36,6 @@ module Gitlab } end - def artifact_upload_ok - { TempPath: JobArtifactUploader.workhorse_upload_path } - end - def send_git_blob(repository, blob) params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_raw_show, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) { diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index 4a3c40f88eb..5dc85b2baea 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -1,8 +1,10 @@ require "tempfile" +require "tmpdir" require "fileutils" -# Taken from: Rack::Test::UploadedFile class UploadedFile + InvalidPathError = Class.new(StandardError) + # The filename, *not* including the path, of the "uploaded" file attr_reader :original_filename @@ -12,14 +14,46 @@ class UploadedFile # The content type of the "uploaded" file attr_accessor :content_type - def initialize(path, filename, content_type = "text/plain") - raise "#{path} file does not exist" unless ::File.exist?(path) + attr_reader :remote_id + attr_reader :sha256 + + def initialize(path, filename: nil, content_type: "application/octet-stream", sha256: nil, remote_id: nil) + raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path) @content_type = content_type @original_filename = filename || ::File.basename(path) + @content_type = content_type + @sha256 = sha256 + @remote_id = remote_id @tempfile = File.new(path, 'rb') end + def self.from_params(params, field, upload_path) + unless params["#{field}.path"] + raise InvalidPathError, "file is invalid" if params["#{field}.remote_id"] + + return + end + + file_path = File.realpath(params["#{field}.path"]) + + unless self.allowed_path?(file_path, [upload_path, Dir.tmpdir].compact) + raise InvalidPathError, "insecure path used '#{file_path}'" + end + + UploadedFile.new(file_path, + filename: params["#{field}.name"], + content_type: params["#{field}.type"] || 'application/octet-stream', + sha256: params["#{field}.sha256"], + remote_id: params["#{field}.remote_id"]) + end + + def self.allowed_path?(file_path, paths) + paths.any? do |path| + File.exist?(path) && file_path.start_with?(File.realpath(path)) + end + end + def path @tempfile.path end diff --git a/spec/initializers/artifacts_direct_upload_support_spec.rb b/spec/initializers/artifacts_direct_upload_support_spec.rb new file mode 100644 index 00000000000..bfb71da3388 --- /dev/null +++ b/spec/initializers/artifacts_direct_upload_support_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe 'Artifacts direct upload support' do + subject do + load Rails.root.join('config/initializers/artifacts_direct_upload_support.rb') + end + + let(:connection) do + { provider: provider } + end + + before do + stub_artifacts_setting( + object_store: { + enabled: enabled, + direct_upload: direct_upload, + connection: connection + }) + end + + context 'when object storage is enabled' do + let(:enabled) { true } + + context 'when direct upload is enabled' do + let(:direct_upload) { true } + + context 'when provider is Google' do + let(:provider) { 'Google' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end + + context 'when connection is empty' do + let(:connection) { nil } + + it 'raises an error' do + expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/ + end + end + + context 'when other provider is used' do + let(:provider) { 'AWS' } + + it 'raises an error' do + expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/ + end + end + end + + context 'when direct upload is disabled' do + let(:direct_upload) { false } + let(:provider) { 'AWS' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end + end + + context 'when object storage is disabled' do + let(:enabled) { false } + let(:direct_upload) { false } + let(:provider) { 'AWS' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end +end diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb new file mode 100644 index 00000000000..cc99e7e8911 --- /dev/null +++ b/spec/lib/uploaded_file_spec.rb @@ -0,0 +1,116 @@ +require 'spec_helper' + +describe UploadedFile do + describe ".from_params" do + let(:temp_dir) { Dir.tmpdir } + let(:temp_file) { Tempfile.new("test", temp_dir) } + let(:upload_path) { nil } + + subject do + described_class.from_params(params, :file, upload_path) + end + + before do + FileUtils.touch(temp_file) + end + + after do + FileUtils.rm_f(temp_file) + FileUtils.rm_r(upload_path) if upload_path + end + + context 'when valid file is specified' do + context 'only local path is specified' do + let(:params) do + { 'file.path' => temp_file.path } + end + + it "succeeds" do + is_expected.not_to be_nil + end + + it "generates filename from path" do + expect(subject.original_filename).to eq(::File.basename(temp_file.path)) + end + end + + context 'all parameters are specified' do + let(:params) do + { 'file.path' => temp_file.path, + 'file.name' => 'my_file.txt', + 'file.type' => 'my/type', + 'file.sha256' => 'sha256', + 'file.remote_id' => 'remote_id' } + end + + it "succeeds" do + is_expected.not_to be_nil + end + + it "generates filename from path" do + expect(subject.original_filename).to eq('my_file.txt') + expect(subject.content_type).to eq('my/type') + expect(subject.sha256).to eq('sha256') + expect(subject.remote_id).to eq('remote_id') + end + end + end + + context 'when no params are specified' do + let(:params) do + {} + end + + it "does not return an object" do + is_expected.to be_nil + end + end + + context 'when only remote id is specified' do + let(:params) do + { 'file.remote_id' => 'remote_id' } + end + + it "raises an error" do + expect { subject }.to raise_error(UploadedFile::InvalidPathError, /file is invalid/) + end + end + + context 'when verifying allowed paths' do + let(:params) do + { 'file.path' => temp_file.path } + end + + context 'when file is stored in system temporary folder' do + let(:temp_dir) { Dir.tmpdir } + + it "succeeds" do + is_expected.not_to be_nil + end + end + + context 'when file is stored in user provided upload path' do + let(:upload_path) { Dir.mktmpdir } + let(:temp_dir) { upload_path } + + it "succeeds" do + is_expected.not_to be_nil + end + end + + context 'when file is stored outside of user provided upload path' do + let!(:generated_dir) { Dir.mktmpdir } + let!(:temp_dir) { Dir.mktmpdir } + + before do + # We overwrite default temporary path + allow(Dir).to receive(:tmpdir).and_return(generated_dir) + end + + it "raises an error" do + expect { subject }.to raise_error(UploadedFile::InvalidPathError, /insecure path used/) + end + end + end + end +end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 4f3420cc0ad..28d51ac86c6 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -950,12 +950,53 @@ describe API::Runner do describe 'POST /api/v4/jobs/:id/artifacts/authorize' do context 'when using token as parameter' do - it 'authorizes posting artifacts to running job' do - authorize_artifacts_with_token_in_params + context 'posting artifacts to running job' do + subject do + authorize_artifacts_with_token_in_params + end - expect(response).to have_gitlab_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - expect(json_response['TempPath']).not_to be_nil + shared_examples 'authorizes local file' do + it 'succeeds' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path) + expect(json_response['RemoteObject']).to be_nil + end + end + + context 'when using local storage' do + it_behaves_like 'authorizes local file' + end + + context 'when using remote storage' do + context 'when direct upload is enabled' do + before do + stub_artifacts_object_storage(enabled: true, direct_upload: true) + end + + it 'succeeds' do + subject + + expect(response).to have_gitlab_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path) + expect(json_response['RemoteObject']).to have_key('ID') + expect(json_response['RemoteObject']).to have_key('GetURL') + expect(json_response['RemoteObject']).to have_key('StoreURL') + expect(json_response['RemoteObject']).to have_key('DeleteURL') + end + end + + context 'when direct upload is disabled' do + before do + stub_artifacts_object_storage(enabled: true, direct_upload: false) + end + + it_behaves_like 'authorizes local file' + end + end end it 'fails to post too large artifact' do @@ -1051,20 +1092,45 @@ describe API::Runner do end end - context 'when uses regular file post' do - before do - upload_artifacts(file_upload, headers_with_token, false) - end - - it_behaves_like 'successful artifacts upload' - end - context 'when uses accelerated file post' do - before do - upload_artifacts(file_upload, headers_with_token, true) + context 'for file stored locally' do + before do + upload_artifacts(file_upload, headers_with_token) + end + + it_behaves_like 'successful artifacts upload' end - it_behaves_like 'successful artifacts upload' + context 'for file stored remotelly' do + let!(:fog_connection) do + stub_artifacts_object_storage(direct_upload: true) + end + + before do + fog_connection.directories.get('artifacts').files.create( + key: 'tmp/upload/12312300', + body: 'content' + ) + + upload_artifacts(file_upload, headers_with_token, + { 'file.remote_id' => remote_id }) + end + + context 'when valid remote_id is used' do + let(:remote_id) { '12312300' } + + it_behaves_like 'successful artifacts upload' + end + + context 'when invalid remote_id is used' do + let(:remote_id) { 'invalid id' } + + it 'responds with bad request' do + expect(response).to have_gitlab_http_status(500) + expect(json_response['message']).to eq("Missing file") + end + end + end end context 'when using runners token' do @@ -1208,15 +1274,19 @@ describe API::Runner do end context 'when artifacts are being stored outside of tmp path' do + let(:new_tmpdir) { Dir.mktmpdir } + before do + # init before overwriting tmp dir + file_upload + # by configuring this path we allow to pass file from @tmpdir only # but all temporary files are stored in system tmp directory - @tmpdir = Dir.mktmpdir - allow(JobArtifactUploader).to receive(:workhorse_upload_path).and_return(@tmpdir) + allow(Dir).to receive(:tmpdir).and_return(new_tmpdir) end after do - FileUtils.remove_entry @tmpdir + FileUtils.remove_entry(new_tmpdir) end it' "fails to post artifacts for outside of tmp path"' do @@ -1226,12 +1296,11 @@ describe API::Runner do end end - def upload_artifacts(file, headers = {}, accelerated = true) - params = if accelerated - { 'file.path' => file.path, 'file.name' => file.original_filename } - else - { 'file' => file } - end + def upload_artifacts(file, headers = {}, params = {}) + params = params.merge({ + 'file.path' => file.path, + 'file.name' => file.original_filename + }) post api("/jobs/#{job.id}/artifacts"), params, headers end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 1e6bd993c08..f80abb06fca 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1016,7 +1016,7 @@ describe 'Git LFS API and storage' do it_behaves_like 'a valid response' do it 'responds with status 200, location of lfs remote store and object details' do - expect(json_response['TempPath']).to be_nil + expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) expect(json_response['RemoteObject']).to have_key('ID') expect(json_response['RemoteObject']).to have_key('GetURL') expect(json_response['RemoteObject']).to have_key('StoreURL') @@ -1073,7 +1073,9 @@ describe 'Git LFS API and storage' do ['123123', '../../123123'].each do |remote_id| context "with invalid remote_id: #{remote_id}" do subject do - put_finalize_with_args('file.remote_id' => remote_id) + put_finalize(with_tempfile: true, args: { + 'file.remote_id' => remote_id + }) end it 'responds with status 403' do @@ -1093,9 +1095,10 @@ describe 'Git LFS API and storage' do end subject do - put_finalize_with_args( + put_finalize(with_tempfile: true, args: { 'file.remote_id' => '12312300', - 'file.name' => 'name') + 'file.name' => 'name' + }) end it 'responds with status 200' do @@ -1331,7 +1334,7 @@ describe 'Git LFS API and storage' do put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, authorize_headers end - def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false) + def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, args: {}) upload_path = LfsObjectUploader.workhorse_local_upload_path file_path = upload_path + '/' + lfs_tmp if lfs_tmp @@ -1340,12 +1343,12 @@ describe 'Git LFS API and storage' do FileUtils.touch(file_path) end - args = { + extra_args = { 'file.path' => file_path, 'file.name' => File.basename(file_path) - }.compact + } - put_finalize_with_args(args) + put_finalize_with_args(args.merge(extra_args).compact) end def put_finalize_with_args(args) diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index bad1d34df3a..a75a3eaefcb 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -45,6 +45,10 @@ module StubConfiguration allow(Gitlab.config.lfs).to receive_messages(to_settings(messages)) end + def stub_artifacts_setting(messages) + allow(Gitlab.config.artifacts).to receive_messages(to_settings(messages)) + end + def stub_storage_settings(messages) messages.deep_stringify_keys! diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 17d508d0146..16455e2517b 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -516,108 +516,46 @@ describe ObjectStorage do end end - describe '#store_workhorse_file!' do + describe '#cache!' do subject do - uploader.store_workhorse_file!(params, :file) + uploader.cache!(uploaded_file) end context 'when local file is used' do context 'when valid file is used' do - let(:target_path) do - File.join(uploader_class.root, uploader_class::TMP_UPLOAD_PATH) + let(:uploaded_file) do + fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') end - before do - FileUtils.mkdir_p(target_path) - end + it "properly caches the file" do + subject - context 'when no filename is specified' do - let(:params) do - { "file.path" => "test/file" } - end - - it 'raises an error' do - expect { subject }.to raise_error(uploader_class::RemoteStoreError, /Missing filename/) - end - end - - context 'when invalid file is specified' do - let(:file_path) do - File.join(target_path, "..", "test.file") - end - - before do - FileUtils.touch(file_path) - end - - let(:params) do - { "file.path" => file_path, - "file.name" => "my_file.txt" } - end - - it 'raises an error' do - expect { subject }.to raise_error(uploader_class::RemoteStoreError, /Bad file path/) - end - end - - context 'when filename is specified' do - let(:params) do - { "file.path" => tmp_file, - "file.name" => "my_file.txt" } - end - - let(:tmp_file) { Tempfile.new('filename', target_path) } - - before do - FileUtils.touch(tmp_file) - end - - after do - FileUtils.rm_f(tmp_file) - end - - it 'succeeds' do - expect { subject }.not_to raise_error - - expect(uploader).to be_exists - end - - it 'proper path is being used' do - subject - - expect(uploader.path).to start_with(uploader_class.root) - expect(uploader.path).to end_with("my_file.txt") - end - - it 'source file to not exist' do - subject - - expect(File.exist?(tmp_file.path)).to be_falsey - end + expect(uploader).to be_exists + expect(uploader.path).to start_with(uploader_class.root) + expect(uploader.filename).to eq('rails_sample.jpg') end end end context 'when remote file is used' do + let(:temp_file) { Tempfile.new("test") } + let!(:fog_connection) do stub_uploads_object_storage(uploader_class) end + before do + FileUtils.touch(temp_file) + end + + after do + FileUtils.rm_f(temp_file) + end + context 'when valid file is used' do - context 'when no filename is specified' do - let(:params) do - { "file.remote_id" => "test/123123" } - end - - it 'raises an error' do - expect { subject }.to raise_error(uploader_class::RemoteStoreError, /Missing filename/) - end - end - context 'when invalid file is specified' do - let(:params) do - { "file.remote_id" => "../test/123123", - "file.name" => "my_file.txt" } + let(:uploaded_file) do + UploadedFile.new(temp_file.path, remote_id: "../test/123123") end it 'raises an error' do @@ -626,9 +564,8 @@ describe ObjectStorage do end context 'when non existing file is specified' do - let(:params) do - { "file.remote_id" => "test/12312300", - "file.name" => "my_file.txt" } + let(:uploaded_file) do + UploadedFile.new(temp_file.path, remote_id: "test/123123") end it 'raises an error' do @@ -636,10 +573,9 @@ describe ObjectStorage do end end - context 'when filename is specified' do - let(:params) do - { "file.remote_id" => "test/123123", - "file.name" => "my_file.txt" } + context 'when valid file is specified' do + let(:uploaded_file) do + UploadedFile.new(temp_file.path, filename: "my_file.txt", remote_id: "test/123123") end let!(:fog_file) do @@ -649,36 +585,37 @@ describe ObjectStorage do ) end - it 'succeeds' do + it 'file to be cached and remote stored' do expect { subject }.not_to raise_error expect(uploader).to be_exists - end - - it 'path to not be temporary' do - subject - + expect(uploader).to be_cached expect(uploader.path).not_to be_nil - expect(uploader.path).not_to include('tmp/upload') - expect(uploader.url).to include('/my_file.txt') + expect(uploader.path).not_to include('tmp/cache') + expect(uploader.url).not_to be_nil + expect(uploader.path).not_to include('tmp/cache') + expect(uploader.object_store).to eq(described_class::Store::REMOTE) end - it 'url is used' do - subject + context 'when file is stored' do + subject do + uploader.store!(uploaded_file) + end - expect(uploader.url).not_to be_nil - expect(uploader.url).to include('/my_file.txt') + it 'file to be remotely stored in permament location' do + subject + + expect(uploader).to be_exists + expect(uploader).not_to be_cached + expect(uploader.path).not_to be_nil + expect(uploader.path).not_to include('tmp/upload') + expect(uploader.path).not_to include('tmp/cache') + expect(uploader.url).to include('/my_file.txt') + expect(uploader.object_store).to eq(described_class::Store::REMOTE) + end end end end end - - context 'when no file is used' do - let(:params) { {} } - - it 'raises an error' do - expect { subject }.to raise_error(uploader_class::RemoteStoreError, /Bad file/) - end - end end end From 99edc15127d9d23475c94079d53e2893f58c042a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 5 Apr 2018 17:32:03 +0200 Subject: [PATCH 039/146] Put Erased empty state at the beginning --- lib/gitlab/ci/status/build/factory.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index 466a0a989ad..9d2d4170266 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -4,12 +4,12 @@ module Gitlab module Build class Factory < Status::Factory def self.extended_statuses - [[Status::Build::Manual, + [[Status::Build::Erased, + Status::Build::Manual, Status::Build::Canceled, Status::Build::Created, Status::Build::Pending, - Status::Build::Skipped, - Status::Build::Erased], + Status::Build::Skipped], [Status::Build::Cancelable, Status::Build::Retryable], [Status::Build::FailedAllowed, From 33c163b347ca37ae91240be40a05c881c89c7135 Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Mon, 10 Jul 2017 23:33:08 +0000 Subject: [PATCH 040/146] Update index.md to describe leaving out the host in prometheus['listen_address'] to allow public access. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/administration/monitoring/prometheus/index.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index f43c89dad87..3d24812c66a 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -62,7 +62,14 @@ To change the address/port that Prometheus listens on: ``` Replace `localhost:9090` with the address/port you want Prometheus to - listen on. + listen on. If you would like to allow access to Prometheus to hosts other + than `localhost`, leave out the host, or use `0.0.0.0` to allow public access: + + ```ruby + prometheus['listen_address'] = ':9090' + # or + prometheus['listen_address'] = '0.0.0.0:9090' + ``` 1. Save the file and [reconfigure GitLab][reconfigure] for the changes to take effect From a76313763bb1d5ee1375fc451970a976f4931ef5 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Thu, 5 Apr 2018 13:03:38 -0400 Subject: [PATCH 041/146] Override the Prometheus server name --- vendor/prometheus/values.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml index 859f2ad82a4..c432be72163 100644 --- a/vendor/prometheus/values.yaml +++ b/vendor/prometheus/values.yaml @@ -14,6 +14,7 @@ rbac: create: false server: + fullnameOverride: "prometheus-prometheus-server" image: tag: v2.1.0 From 22423d284704c3ec2f7715736a75155d14ff413d Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 4 Apr 2018 18:43:33 -0300 Subject: [PATCH 042/146] Show issues of subgroups in group-level issue board --- .../boards/components/board_sidebar.js | 6 +---- .../boards/components/issue_card_inner.js | 13 ++-------- .../boards/components/sidebar/remove_issue.js | 6 +---- .../boards/filtered_search_boards.js | 1 + app/assets/javascripts/boards/models/issue.js | 6 +++-- app/controllers/boards/issues_controller.rb | 3 ++- .../groups/milestones_controller.rb | 2 +- app/models/issue.rb | 12 ++++++--- app/services/boards/issues/list_service.rb | 5 ++++ app/services/issuable_base_service.rb | 3 ++- .../boards/components/_sidebar.html.haml | 2 +- .../components/sidebar/_assignee.html.haml | 3 +-- .../components/sidebar/_due_date.html.haml | 3 +-- .../components/sidebar/_labels.html.haml | 3 +-- .../components/sidebar/_milestone.html.haml | 3 +-- changelogs/unreleased/issue_44270.yml | 5 ++++ spec/features/labels_hierarchy_spec.rb | 6 +++-- .../boards/issues/list_service_spec.rb | 25 +++++++++++++++---- 18 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 changelogs/unreleased/issue_44270.yml diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index a44969272a1..c4ee4f6c855 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -60,10 +60,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({ this.issue = this.detail.issue; this.list = this.detail.list; - - this.$nextTick(() => { - this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate; - }); }, deep: true }, @@ -91,7 +87,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ saveAssignees () { this.loadingAssignees = true; - gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint) + gl.issueBoards.BoardsStore.detail.issue.update() .then(() => { this.loadingAssignees = false; }) diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index 8aee5b23c76..84fe9b1288a 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -68,15 +68,6 @@ gl.issueBoards.IssueCardInner = Vue.extend({ return this.issue.assignees.length > this.numberOverLimit; }, - cardUrl() { - let baseUrl = this.issueLinkBase; - - if (this.groupId && this.issue.project) { - baseUrl = this.issueLinkBase.replace(':project_path', this.issue.project.path); - } - - return `${baseUrl}/${this.issue.iid}`; - }, issueId() { if (this.issue.iid) { return `#${this.issue.iid}`; @@ -153,13 +144,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({ /> {{ issue.title }} - {{ issueId }} + {{ issue.referencePath }}
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index 09c683ff621..0a0820ec5fd 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -17,14 +17,10 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ type: Object, required: true, }, - issueUpdate: { - type: String, - required: true, - }, }, computed: { updateUrl() { - return this.issueUpdate.replace(':project_path', this.issue.project.path); + return this.issue.path; }, }, methods: { diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index fb40b9f5565..70367c4f711 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -6,6 +6,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager { constructor(store, updateUrl = false, cantEdit = []) { super({ page: 'boards', + isGroupDecendent: true, stateFiltersSelector: '.issues-state-filters', }); diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 4c5079efc8b..85f98615cf4 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -23,6 +23,8 @@ class ListIssue { }; this.isLoading = {}; this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; + this.referencePath = obj.reference_path; + this.path = obj.real_path; this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; this.milestone_id = obj.milestone_id; this.project_id = obj.project_id; @@ -98,7 +100,7 @@ class ListIssue { this.isLoading[key] = value; } - update (url) { + update () { const data = { issue: { milestone_id: this.milestone ? this.milestone.id : null, @@ -113,7 +115,7 @@ class ListIssue { } const projectPath = this.project ? this.project.path : ''; - return Vue.http.patch(url.replace(':project_path', projectPath), data); + return Vue.http.patch(this.path + '.json', data); } } diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 19dbee84c11..7d7ff217e5d 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -96,7 +96,8 @@ module Boards resource.as_json( only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], labels: true, - sidebar_endpoints: true, + issue_endpoints: true, + include_full_project_path: board.group_board?, include: { project: { only: [:id, :path] }, assignees: { only: [:id, :name, :username], methods: [:avatar_url] }, diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index acf6aaf57f4..5903689dc62 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -12,7 +12,7 @@ class Groups::MilestonesController < Groups::ApplicationController @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end format.json do - render json: milestones.map { |m| m.for_display.slice(:title, :name) } + render json: milestones.map { |m| m.for_display.slice(:id, :title, :name) } end end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 13abc6c1a0d..13d9e42bcc8 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -272,11 +272,17 @@ class Issue < ActiveRecord::Base def as_json(options = {}) super(options).tap do |json| - if options.key?(:sidebar_endpoints) && project + if options.key?(:issue_endpoints) && project url_helper = Gitlab::Routing.url_helpers - json.merge!(issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'), - toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self)) + issue_reference = options[:include_full_project_path] ? to_reference(full: true) : to_reference + + json.merge!( + reference_path: issue_reference, + real_path: url_helper.project_issue_path(project, self), + issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'), + toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self) + ) end if options.key?(:labels) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index ecd74b74f8a..8a1796a1096 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -35,6 +35,7 @@ module Boards def filter_params set_parent set_state + set_scope params end @@ -51,6 +52,10 @@ module Boards params[:state] = list && list.closed? ? 'closed' : 'opened' end + def set_scope + params[:include_subgroups] = true if board.group_board? + end + def board_label_ids @board_label_ids ||= board.lists.movable.pluck(:label_id) end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 91ec702fbc6..1f67e3ecf9d 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -51,9 +51,10 @@ class IssuableBaseService < BaseService return unless milestone_id params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE + group_ids = project.group&.self_and_ancestors&.pluck(:id) milestone = - Milestone.for_projects_and_groups([project.id], [project.group&.id]).find_by_id(milestone_id) + Milestone.for_projects_and_groups([project.id], group_ids).find_by_id(milestone_id) params[:milestone_id] = '' unless milestone end diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml index 8e5e32e9f16..b385cc3f962 100644 --- a/app/views/shared/boards/components/_sidebar.html.haml +++ b/app/views/shared/boards/components/_sidebar.html.haml @@ -22,6 +22,6 @@ = render "shared/boards/components/sidebar/labels" = render "shared/boards/components/sidebar/notifications" %remove-btn{ ":issue" => "issue", - ":issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'", + ":issue-update" => "issue.sidebarInfoEndpoint", ":list" => "list", "v-if" => "canRemove" } diff --git a/app/views/shared/boards/components/sidebar/_assignee.html.haml b/app/views/shared/boards/components/sidebar/_assignee.html.haml index 3d2e8471a60..1374da9d82c 100644 --- a/app/views/shared/boards/components/sidebar/_assignee.html.haml +++ b/app/views/shared/boards/components/sidebar/_assignee.html.haml @@ -21,8 +21,7 @@ .dropdown - dropdown_options = issue_assignees_dropdown_options %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: board_sidebar_user_data, - ":data-issuable-id" => "issue.iid", - ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" } + ":data-issuable-id" => "issue.iid" } = dropdown_options[:title] = icon("chevron-down") .dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml index db794d6f855..c1a97269954 100644 --- a/app/views/shared/boards/components/sidebar/_due_date.html.haml +++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml @@ -22,8 +22,7 @@ ":value" => "issue.dueDate" } .dropdown %button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button', - data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" }, - ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" } + data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" }} %span.dropdown-toggle-text Due date = icon('chevron-down') .dropdown-menu.dropdown-menu-due-date diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml index dfc0f9be321..3c514ededc5 100644 --- a/app/views/shared/boards/components/sidebar/_labels.html.haml +++ b/app/views/shared/boards/components/sidebar/_labels.html.haml @@ -26,8 +26,7 @@ project_id: @project&.try(:id), labels: labels_filter_path(false), namespace_path: @namespace_path, - project_path: @project.try(:path) }, - ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" } + project_path: @project.try(:path) }} %span.dropdown-toggle-text Label = icon('chevron-down') diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml index d09c7c218e0..f51c4a97f2b 100644 --- a/app/views/shared/boards/components/sidebar/_milestone.html.haml +++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml @@ -18,8 +18,7 @@ .dropdown %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" }, ":data-selected" => "milestoneTitle", - ":data-issuable-id" => "issue.iid", - ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" } + ":data-issuable-id" => "issue.iid" } Milestone = icon("chevron-down") .dropdown-menu.dropdown-select.dropdown-menu-selectable diff --git a/changelogs/unreleased/issue_44270.yml b/changelogs/unreleased/issue_44270.yml new file mode 100644 index 00000000000..6234162be30 --- /dev/null +++ b/changelogs/unreleased/issue_44270.yml @@ -0,0 +1,5 @@ +--- +title: Show issues of subgroups in group-level issue board +merge_request: +author: +type: changed diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index 99e1fb30d5b..d4a49a1cbf4 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -116,10 +116,12 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests if board - pending("Waiting for https://gitlab.com/gitlab-org/gitlab-ce/issues/44270") - select_label_on_dropdown(group_label_3.title) + expect(page).to have_selector('.card-title') do |card| + expect(card).not_to have_selector('a', text: labeled_issue_2.title) + end + expect(page).to have_selector('.card-title') do |card| expect(card).to have_selector('a', text: labeled_issue_3.title) end diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index b4efa3e44b6..8d67bf67747 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -48,10 +48,8 @@ describe Boards::Issues::ListService do context 'when parent is a group' do let(:user) { create(:user) } - let(:group) { create(:group) } let(:project) { create(:project, :empty_repo, namespace: group) } let(:project1) { create(:project, :empty_repo, namespace: group) } - let(:board) { create(:board, group: group) } let(:m1) { create(:milestone, group: group) } let(:m2) { create(:milestone, group: group) } @@ -92,13 +90,30 @@ describe Boards::Issues::ListService do let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1, p1_project1]) } let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development]) } - let(:parent) { group } - before do group.add_developer(user) end - it_behaves_like 'issues list service' + context 'and group has no parent' do + let(:parent) { group } + let(:group) { create(:group) } + let(:board) { create(:board, group: group) } + + it_behaves_like 'issues list service' + end + + context 'and group is an ancestor' do + let(:parent) { create(:group) } + let(:group) { create(:group, parent: parent) } + let!(:backlog) { create(:backlog_list, board: board) } + let(:board) { create(:board, group: parent) } + + before do + parent.add_developer(user) + end + + it_behaves_like 'issues list service' + end end end end From 62574be35d5a92c6c825912e1ab9969c5acdb7d9 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Apr 2018 12:28:49 -0300 Subject: [PATCH 043/146] Fix specs --- app/models/milestone.rb | 4 ++-- spec/features/labels_hierarchy_spec.rb | 6 ++---- spec/javascripts/boards/issue_card_spec.js | 2 ++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index dafae58d121..a66a0015827 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -34,8 +34,8 @@ class Milestone < ActiveRecord::Base scope :for_projects_and_groups, -> (project_ids, group_ids) do conditions = [] - conditions << arel_table[:project_id].in(project_ids) if project_ids.compact.any? - conditions << arel_table[:group_id].in(group_ids) if group_ids.compact.any? + conditions << arel_table[:project_id].in(project_ids) if project_ids&.compact&.any? + conditions << arel_table[:group_id].in(group_ids) if group_ids&.compact&.any? where(conditions.reduce(:or)) end diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index d4a49a1cbf4..3e05e7b7f38 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -115,9 +115,9 @@ feature 'Labels Hierarchy', :js, :nested_groups do it 'filters by descendant group labels' do wait_for_requests - if board - select_label_on_dropdown(group_label_3.title) + select_label_on_dropdown(group_label_3.title) + if board expect(page).to have_selector('.card-title') do |card| expect(card).not_to have_selector('a', text: labeled_issue_2.title) end @@ -126,8 +126,6 @@ feature 'Labels Hierarchy', :js, :nested_groups do expect(card).to have_selector('a', text: labeled_issue_3.title) end else - select_label_on_dropdown(group_label_3.title) - expect_issues_list_count(1) expect(page).to have_selector('span.issue-title-text', text: labeled_issue_3.title) end diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index 37088a6421c..aa01f7d6d09 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -41,6 +41,8 @@ describe('Issue card component', () => { confidential: false, labels: [list.label], assignees: [], + reference_path: '#1', + real_path: '/test/1' }); component = new Vue({ From 7c6f4adac9bc2f0bba46a4267f1081c7542e4dc4 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Apr 2018 14:57:48 -0300 Subject: [PATCH 044/146] Fix more specs --- app/assets/javascripts/boards/models/issue.js | 2 +- app/services/boards/issues/list_service.rb | 2 +- spec/fixtures/api/schemas/issue.json | 2 ++ spec/javascripts/boards/issue_card_spec.js | 2 +- spec/services/boards/issues/list_service_spec.rb | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 85f98615cf4..b381d48d625 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -115,7 +115,7 @@ class ListIssue { } const projectPath = this.project ? this.project.path : ''; - return Vue.http.patch(this.path + '.json', data); + return Vue.http.patch(`${this.path}.json`, data); } } diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 8a1796a1096..ac70a99c2c5 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -53,7 +53,7 @@ module Boards end def set_scope - params[:include_subgroups] = true if board.group_board? + params[:include_subgroups] = board.group_board? end def board_label_ids diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json index b579e32c9aa..8833825e3fb 100644 --- a/spec/fixtures/api/schemas/issue.json +++ b/spec/fixtures/api/schemas/issue.json @@ -15,6 +15,8 @@ "relative_position": { "type": "integer" }, "issue_sidebar_endpoint": { "type": "string" }, "toggle_subscription_endpoint": { "type": "string" }, + "reference_path": { "type": "string" }, + "real_path": { "type": "string" }, "project": { "id": { "type": "integer" }, "path": { "type": "string" } diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index aa01f7d6d09..be1ea0b57b4 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -42,7 +42,7 @@ describe('Issue card component', () => { labels: [list.label], assignees: [], reference_path: '#1', - real_path: '/test/1' + real_path: '/test/1', }); component = new Vue({ diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 8d67bf67747..27a7bf0e605 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -102,7 +102,7 @@ describe Boards::Issues::ListService do it_behaves_like 'issues list service' end - context 'and group is an ancestor' do + context 'and group is an ancestor', :nested_groups do let(:parent) { create(:group) } let(:group) { create(:group, parent: parent) } let!(:backlog) { create(:backlog_list, board: board) } From b9c0cf1318d663edc3af593f88521e86e0614824 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Apr 2018 15:14:04 -0300 Subject: [PATCH 045/146] Improve specs and docs --- app/services/issues/update_service.rb | 2 +- doc/user/project/issue_board.md | 3 +-- spec/services/issues/update_service_spec.rb | 6 ++++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 4161932ad2a..1374f10c586 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -90,7 +90,7 @@ module Issues issue = if board_group_id - IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id) + IssuesFinder.new(current_user, group_id: board_group_id, include_subgroups: true).find_by(id: id) else project.issues.find(id) end diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index b4a842f33d6..7eab825fa32 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -240,8 +240,7 @@ Issue Board, that is create/delete lists and drag issues around. >Introduced in GitLab 10.6 Group issue board is analogous to project-level issue board and it is accessible at the group -navigation level. A group-level issue board allows you to view all issues from all projects in that group -(currently, it does not see issues from projects in subgroups). Similarly, you can only filter by group labels for these +navigation level. A group-level issue board allows you to view all issues from all projects in that group or descendant subgroups. Similarly, you can only filter by group labels for these boards. When updating milestones and labels for an issue through the sidebar update mechanism, again only group-level objects are available. diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index f95474208f3..23b1134b5a3 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -97,11 +97,13 @@ describe Issues::UpdateService, :mailer do expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) end - context 'when moving issue between issues from different projects' do + context 'when moving issue between issues from different projects', :nested_groups do let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + let(:project_1) { create(:project, namespace: group) } let(:project_2) { create(:project, namespace: group) } - let(:project_3) { create(:project, namespace: group) } + let(:project_3) { create(:project, namespace: subgroup) } let(:issue_1) { create(:issue, project: project_1) } let(:issue_2) { create(:issue, project: project_2) } From b099c6ff9fcbd81beb75fd12bff8d1d5b9c16083 Mon Sep 17 00:00:00 2001 From: m b Date: Fri, 23 Mar 2018 00:09:26 -0500 Subject: [PATCH 046/146] Deleting a MR you are assigned to should decrements counter The merge request counter in the UI was not decreasing when a merge request was deleting. This was just due to the cache not being refreshed on a delete action. fixes: https://gitlab.com/gitlab-org/gitlab-ce/issues/44458 --- app/models/user.rb | 5 ----- app/services/issuable/destroy_service.rb | 1 + changelogs/unreleased/ui-mr-counter-cache.yml | 5 +++++ spec/services/issuable/destroy_service_spec.rb | 14 ++++++++++++-- 4 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/ui-mr-counter-cache.yml diff --git a/app/models/user.rb b/app/models/user.rb index 7b6857a0d34..ce56b39b1c8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1046,11 +1046,6 @@ class User < ActiveRecord::Base end end - def update_cache_counts - assigned_open_merge_requests_count(force: true) - assigned_open_issues_count(force: true) - end - def update_todos_count_cache todos_done_count(force: true) todos_pending_count(force: true) diff --git a/app/services/issuable/destroy_service.rb b/app/services/issuable/destroy_service.rb index 7197a426a72..0b1a33518c6 100644 --- a/app/services/issuable/destroy_service.rb +++ b/app/services/issuable/destroy_service.rb @@ -4,6 +4,7 @@ module Issuable TodoService.new.destroy_target(issuable) do |issuable| if issuable.destroy issuable.update_project_counter_caches + issuable.assignees.each(&:invalidate_cache_counts) end end end diff --git a/changelogs/unreleased/ui-mr-counter-cache.yml b/changelogs/unreleased/ui-mr-counter-cache.yml new file mode 100644 index 00000000000..5e241bfe93f --- /dev/null +++ b/changelogs/unreleased/ui-mr-counter-cache.yml @@ -0,0 +1,5 @@ +--- +title: Deleting a MR you are assigned to should decrements counter +merge_request: 17951 +author: m b +type: fixed diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb index 0a3647a814f..8ccbba7fa58 100644 --- a/spec/services/issuable/destroy_service_spec.rb +++ b/spec/services/issuable/destroy_service_spec.rb @@ -8,7 +8,7 @@ describe Issuable::DestroyService do describe '#execute' do context 'when issuable is an issue' do - let!(:issue) { create(:issue, project: project, author: user) } + let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) } it 'destroys the issue' do expect { service.execute(issue) }.to change { project.issues.count }.by(-1) @@ -26,10 +26,15 @@ describe Issuable::DestroyService do expect { service.execute(issue) } .to change { user.todos_pending_count }.from(1).to(0) end + + it 'invalidates the issues count cache for the assignees' do + expect_any_instance_of(User).to receive(:invalidate_cache_counts).once + service.execute(issue) + end end context 'when issuable is a merge request' do - let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user) } + let!(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user, assignee: user) } it 'destroys the merge request' do expect { service.execute(merge_request) }.to change { project.merge_requests.count }.by(-1) @@ -41,6 +46,11 @@ describe Issuable::DestroyService do service.execute(merge_request) end + it 'invalidates the merge request caches for the MR assignee' do + expect_any_instance_of(User).to receive(:invalidate_cache_counts).once + service.execute(merge_request) + end + it 'updates the todo caches for users with todos on the merge request' do create(:todo, target: merge_request, user: user, author: user, project: project) From 0a111c91d22f7bfe52cb47f6ffa4ecbe622c6a5d Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Apr 2018 15:31:46 -0300 Subject: [PATCH 047/146] Fix rubocop --- app/views/shared/boards/components/sidebar/_labels.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml index 3c514ededc5..87e6b52f46e 100644 --- a/app/views/shared/boards/components/sidebar/_labels.html.haml +++ b/app/views/shared/boards/components/sidebar/_labels.html.haml @@ -26,7 +26,7 @@ project_id: @project&.try(:id), labels: labels_filter_path(false), namespace_path: @namespace_path, - project_path: @project.try(:path) }} + project_path: @project.try(:path) } } %span.dropdown-toggle-text Label = icon('chevron-down') From b72da4e3d71a8b6ae022221453d7b4fa353ff6d9 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 5 Apr 2018 20:20:39 +0200 Subject: [PATCH 048/146] Update GitLab.com settings with current state --- doc/user/gitlab_com/index.md | 106 +++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 17 deletions(-) diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index d5f77191938..7baccb796c6 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -72,15 +72,23 @@ The maximum size your Git repository is allowed to be including LFS. ## Shared Runners Shared Runners on GitLab.com run in [autoscale mode] and powered by -DigitalOcean. Autoscaling means reduced waiting times to spin up builds, -and isolated VMs for each project, thus maximizing security. +Google Cloud Platform and DigitalOcean. Autoscaling means reduced +waiting times to spin up CI/CD jobs, and isolated VMs for each project, +thus maximizing security. They're free to use for public open source projects and limited to 2000 CI minutes per month per group for private projects. Read about all [GitLab.com plans](https://about.gitlab.com/pricing/). -All your builds run on 2GB (RAM) ephemeral instances, with CoreOS and the latest -Docker Engine installed. The default region of the VMs is NYC. +In case of DigitalOcean based Runners, all your CI/CD jobs run on ephemeral +instances with 2GB of RAM, CoreOS and the latest Docker Engine installed. +Instances provide 2 vCPUs and 60GB of SSD disk space. The default region of the +VMs is NYC1. + +In case of Google Cloud Platform based Runners, all your CI/CD jobs run on +ephemeral instances with 3.75GB of RAM, CoreOS and the latest Docker Engine +installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default +region of the VMs is US East1. Below are the shared Runners settings. @@ -88,52 +96,116 @@ Below are the shared Runners settings. | ----------- | ----------------- | ---------- | | [GitLab Runner] | [Runner versions dashboard][ci_version_dashboard] | - | | Executor | `docker+machine` | - | -| Default Docker image | `ruby:2.1` | - | +| Default Docker image | `ruby:2.5` | - | | `privileged` (run [Docker in Docker]) | `true` | `false` | -[ci_version_dashboard]: https://monitor.gitlab.net/dashboard/db/ci?refresh=5m&orgId=1&panelId=12&fullscreen&from=now-1h&to=now&var-runner_type=All&var-cache_server=All&var-gl_monitor_fqdn=postgres-01.db.prd.gitlab.com&var-has_minutes=yes&var-hanging_droplets_cleaner=All&var-droplet_zero_machines_cleaner=All&var-runner_job_failure_reason=All&theme=light +[ci_version_dashboard]: https://monitor.gitlab.net/dashboard/db/ci?from=now-1h&to=now&refresh=5m&orgId=1&panelId=12&fullscreen&theme=light ### `config.toml` The full contents of our `config.toml` are: +**DigitalOcean** + ```toml +concurrent = X +check_interval = 1 +metrics_server = "X" +sentry_dsn = "X" + [[runners]] name = "docker-auto-scale" - limit = X request_concurrency = X - url = "https://gitlab.com/ci" + url = "https://gitlab.com/" token = "SHARED_RUNNER_TOKEN" executor = "docker+machine" environment = [ "DOCKER_DRIVER=overlay2" ] + limit = X [runners.docker] - image = "ruby:2.1" + image = "ruby:2.5" privileged = true [runners.machine] - IdleCount = 40 + IdleCount = 20 IdleTime = 1800 + OffPeakPeriods = ["* * * * * sat,sun *"] + OffPeakTimezone = "UTC" + OffPeakIdleCount = 5 + OffPeakIdleTime = 1800 MaxBuilds = 1 + MachineName = "srm-%s" MachineDriver = "digitalocean" - MachineName = "machine-%s-digital-ocean-2gb" MachineOptions = [ - "digitalocean-image=coreos-stable", + "digitalocean-image=X", "digitalocean-ssh-user=core", - "digitalocean-access-token=DIGITAL_OCEAN_ACCESS_TOKEN", "digitalocean-region=nyc1", - "digitalocean-size=2gb", + "digitalocean-size=s-2vcpu-2gb", "digitalocean-private-networking", - "digitalocean-userdata=/etc/gitlab-runner/cloudinit.sh", - "engine-registry-mirror=http://IP_TO_OUR_REGISTRY_MIRROR" + "digitalocean-tags=shared_runners,gitlab_com", + "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR", + "digitalocean-access-token=DIGITAL_OCEAN_ACCESS_TOKEN", ] [runners.cache] Type = "s3" - ServerAddress = "IP_TO_OUR_CACHE_SERVER" + BucketName = "runner" + Insecure = true + Shared = true + ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER" AccessKey = "ACCESS_KEY" SecretKey = "ACCESS_SECRET_KEY" +``` + +**Google Cloud Platform** + +```toml +concurrent = X +check_interval = 1 +metrics_server = "X" +sentry_dsn = "X" + +[[runners]] + name = "docker-auto-scale" + request_concurrency = X + url = "https://gitlab.com/" + token = "SHARED_RUNNER_TOKEN" + executor = "docker+machine" + environment = [ + "DOCKER_DRIVER=overlay2" + ] + limit = X + [runners.docker] + image = "ruby:2.5" + privileged = true + [runners.machine] + IdleCount = 20 + IdleTime = 1800 + OffPeakPeriods = ["* * * * * sat,sun *"] + OffPeakTimezone = "UTC" + OffPeakIdleCount = 5 + OffPeakIdleTime = 1800 + MaxBuilds = 1 + MachineName = "srm-%s" + MachineDriver = "google" + MachineOptions = [ + "google-project=PROJECT", + "google-disk-size=25", + "google-machine-type=n1-standard-1", + "google-username=core", + "google-tags=gitlab-com,srm", + "google-use-internal-ip", + "google-zone=us-east1-d", + "google-machine-image=PROJECT/global/images/IMAGE", + "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR" + ] + [runners.cache] + Type = "s3" BucketName = "runner" + Insecure = true Shared = true + ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER" + AccessKey = "ACCESS_KEY" + SecretKey = "ACCESS_SECRET_KEY" ``` ## Sidekiq From 696daa7ef1d784dfd1521bd5b5cc2cc18d89e96e Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 5 Apr 2018 16:50:00 -0300 Subject: [PATCH 049/146] Fix rubocop --- app/views/shared/boards/components/sidebar/_due_date.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml index c1a97269954..d13b998e6f4 100644 --- a/app/views/shared/boards/components/sidebar/_due_date.html.haml +++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml @@ -22,7 +22,7 @@ ":value" => "issue.dueDate" } .dropdown %button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button', - data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" }} + data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" } } %span.dropdown-toggle-text Due date = icon('chevron-down') .dropdown-menu.dropdown-menu-due-date From d209a05486503f1dd6412c777c1fd29fe02f0a40 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 5 Apr 2018 12:52:20 -0700 Subject: [PATCH 050/146] Use the GitLab version as part of the appearances cache key This prevents us from having to manually flush the cache key in migrations every time a new column is added. Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/5571 --- app/models/appearance.rb | 2 +- changelogs/unreleased/sh-appearance-cache-key-version.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/sh-appearance-cache-key-version.yml diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 2a6406d63c7..fb66dd0b766 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -16,7 +16,7 @@ class Appearance < ActiveRecord::Base has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - CACHE_KEY = 'current_appearance'.freeze + CACHE_KEY = "current_appearance:#{Gitlab::VERSION}".freeze after_commit :flush_redis_cache diff --git a/changelogs/unreleased/sh-appearance-cache-key-version.yml b/changelogs/unreleased/sh-appearance-cache-key-version.yml new file mode 100644 index 00000000000..c17a3dc36cd --- /dev/null +++ b/changelogs/unreleased/sh-appearance-cache-key-version.yml @@ -0,0 +1,5 @@ +--- +title: Use the GitLab version as part of the appearances cache key +merge_request: +author: +type: fixed From b9dfd071ed7260e9a3f470317011bcdcfedd61ed Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 28 Mar 2018 17:39:12 -0300 Subject: [PATCH 051/146] Include subgroup issues when searching for group issues using the API --- changelogs/unreleased/issue_42443.yml | 5 +++++ lib/api/issues.rb | 2 +- spec/requests/api/issues_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/issue_42443.yml diff --git a/changelogs/unreleased/issue_42443.yml b/changelogs/unreleased/issue_42443.yml new file mode 100644 index 00000000000..954fbd98a4b --- /dev/null +++ b/changelogs/unreleased/issue_42443.yml @@ -0,0 +1,5 @@ +--- +title: Include subgroup issues when searching for group issues using the API +merge_request: +author: +type: added diff --git a/lib/api/issues.rb b/lib/api/issues.rb index f74b3b26802..88e7f46c92c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -97,7 +97,7 @@ module API get ":id/issues" do group = find_group!(params[:id]) - issues = paginate(find_issues(group_id: group.id)) + issues = paginate(find_issues(group_id: group.id, include_subgroups: true)) options = { with: Entities::IssueBasic, diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 6614e8cea43..90f9c4ad214 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -384,6 +384,30 @@ describe API::Issues do end let(:base_url) { "/groups/#{group.id}/issues" } + context 'when group has subgroups', :nested_groups do + let(:subgroup_1) { create(:group, parent: group) } + let(:subgroup_2) { create(:group, parent: subgroup_1) } + + let(:subgroup_1_project) { create(:project, namespace: subgroup_1) } + let(:subgroup_2_project) { create(:project, namespace: subgroup_2) } + + let!(:issue_1) { create(:issue, project: subgroup_1_project) } + let!(:issue_2) { create(:issue, project: subgroup_2_project) } + + before do + group.add_developer(user) + end + + it 'also returns subgroups projects issues' do + get api(base_url, user) + + issue_ids = json_response.map { |issue| issue['id'] } + + expect_paginated_array_response(size: 5) + expect(issue_ids).to include(issue_1.id, issue_2.id) + end + end + it 'returns all group issues (including opened and closed)' do get api(base_url, admin) From d54cf868f81ca957c8322661b11e6755d9ea5a85 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Thu, 5 Apr 2018 21:04:42 +0000 Subject: [PATCH 052/146] Resolve "Show `failure_reason` and upgrade tooltips of jobs" --- .../components/graph/job_component.vue | 9 +- app/assets/stylesheets/pages/builds.scss | 2 +- app/controllers/projects/jobs_controller.rb | 8 +- app/models/concerns/presentable.rb | 8 ++ app/presenters/ci/build_presenter.rb | 16 ++++ app/serializers/status_entity.rb | 2 +- app/views/ci/status/_badge.html.haml | 4 +- .../ci/status/_dropdown_graph_badge.html.haml | 7 +- app/views/projects/jobs/_sidebar.html.haml | 8 +- app/views/projects/jobs/show.html.haml | 2 +- ...lure-reason-on-upgrade-tooltip-of-jobs.yml | 5 + lib/gitlab/ci/status/build/factory.rb | 4 +- lib/gitlab/ci/status/build/failed.rb | 40 ++++++++ lib/gitlab/ci/status/build/failed_allowed.rb | 12 ++- lib/gitlab/ci/status/build/retried.rb | 17 ++++ lib/gitlab/ci/status/core.rb | 10 ++ spec/factories/ci/builds.rb | 5 + .../projects/jobs/user_browses_job_spec.rb | 22 +++++ .../projects/jobs/user_browses_jobs_spec.rb | 11 +++ .../projects/pipelines/pipeline_spec.rb | 16 ++++ .../projects/pipelines/pipelines_spec.rb | 17 ++++ .../pipelines/graph/job_component_spec.js | 2 + .../lib/gitlab/ci/status/build/action_spec.rb | 10 ++ .../gitlab/ci/status/build/cancelable_spec.rb | 18 ++++ .../gitlab/ci/status/build/factory_spec.rb | 8 +- .../ci/status/build/failed_allowed_spec.rb | 23 +++++ .../lib/gitlab/ci/status/build/failed_spec.rb | 83 ++++++++++++++++ spec/lib/gitlab/ci/status/build/play_spec.rb | 16 ++++ .../gitlab/ci/status/build/retried_spec.rb | 96 +++++++++++++++++++ .../gitlab/ci/status/build/retryable_spec.rb | 18 ++++ spec/lib/gitlab/ci/status/build/stop_spec.rb | 20 ++++ .../gitlab/ci/status/success_warning_spec.rb | 4 +- spec/presenters/ci/build_presenter_spec.rb | 95 ++++++++++++++++-- spec/serializers/build_serializer_spec.rb | 22 ++++- spec/serializers/job_entity_spec.rb | 26 ++++- spec/serializers/pipeline_entity_spec.rb | 2 +- spec/serializers/stage_entity_spec.rb | 2 +- spec/serializers/status_entity_spec.rb | 2 +- .../projects/jobs/show.html.haml_spec.rb | 3 + 39 files changed, 636 insertions(+), 39 deletions(-) create mode 100644 changelogs/unreleased/44269-show-failure-reason-on-upgrade-tooltip-of-jobs.yml create mode 100644 lib/gitlab/ci/status/build/failed.rb create mode 100644 lib/gitlab/ci/status/build/retried.rb create mode 100644 spec/lib/gitlab/ci/status/build/failed_spec.rb create mode 100644 spec/lib/gitlab/ci/status/build/retried_spec.rb diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index 9b136573135..d501c465a96 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -17,6 +17,7 @@ * "text": "passed", * "label": "passed", * "group": "success", + * "tooltip": "passed", * "details_path": "/root/ci-mock/builds/4256", * "action": { * "icon": "retry", @@ -69,12 +70,12 @@ textBuilder.push(this.job.name); } - if (this.job.name && this.status.label) { + if (this.job.name && this.status.tooltip) { textBuilder.push('-'); } - if (this.status.label) { - textBuilder.push(`${this.job.status.label}`); + if (this.status.tooltip) { + textBuilder.push(`${this.job.status.tooltip}`); } return textBuilder.join(' '); @@ -100,6 +101,7 @@ :title="tooltipText" :class="cssClassJobName" data-container="body" + data-html="true" class="js-pipeline-graph-job-link" > @@ -115,6 +117,7 @@ class="js-job-component-tooltip" :title="tooltipText" :class="cssClassJobName" + data-html="true" data-container="body" > diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 98d460339cd..7a6352e45f1 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -391,7 +391,7 @@ } &:hover { - background-color: $row-hover; + background-color: $dropdown-item-hover-bg; } .icon-retry { diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index ac9eb2047a0..dd12d30a085 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -2,7 +2,6 @@ class Projects::JobsController < Projects::ApplicationController include SendFileUpload before_action :build, except: [:index, :cancel_all] - before_action :authorize_read_build!, only: [:index, :show, :status, :raw, :trace] before_action :authorize_update_build!, @@ -45,8 +44,11 @@ class Projects::JobsController < Projects::ApplicationController end def show - @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC') - @builds = @builds.where("id not in (?)", @build.id) + @builds = @project.pipelines + .find_by_sha(@build.sha) + .builds + .order('id DESC') + .present(current_user: current_user) @pipeline = @build.pipeline respond_to do |format| diff --git a/app/models/concerns/presentable.rb b/app/models/concerns/presentable.rb index 7b33b837004..bc4fbd19a02 100644 --- a/app/models/concerns/presentable.rb +++ b/app/models/concerns/presentable.rb @@ -1,4 +1,12 @@ module Presentable + extend ActiveSupport::Concern + + class_methods do + def present(attributes) + all.map { |klass_object| klass_object.present(attributes) } + end + end + def present(**attributes) Gitlab::View::Presenter::Factory .new(self, attributes) diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index 255475e1fe6..9afebda19be 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -15,6 +15,8 @@ module Ci def status_title if auto_canceled? "Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}" + else + tooltip_for_badge end end @@ -28,5 +30,19 @@ module Ci trigger_request.user_variables end end + + def tooltip_message + "#{subject.name} - #{detailed_status.status_tooltip}" + end + + private + + def tooltip_for_badge + detailed_status.badge_tooltip.capitalize + end + + def detailed_status + @detailed_status ||= subject.detailed_status(user) + end end end diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb index a7c2e21e92b..8e8bda2f9df 100644 --- a/app/serializers/status_entity.rb +++ b/app/serializers/status_entity.rb @@ -2,7 +2,7 @@ class StatusEntity < Grape::Entity include RequestAwareEntity expose :icon, :text, :label, :group - + expose :status_tooltip, as: :tooltip expose :has_details?, as: :has_details expose :details_path diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index 35a3563dff1..5114387984b 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -4,10 +4,10 @@ - css_classes = "ci-status ci-#{status.group} #{'has-tooltip' if title.present?}" - if link && status.has_details? - = link_to status.details_path, class: css_classes, title: title do + = link_to status.details_path, class: css_classes, title: title, data: { html: title.present? } do = sprite_icon(status.icon) = status.text - else - %span{ class: css_classes, title: title } + %span{ class: css_classes, title: title, data: { html: title.present? } } = sprite_icon(status.icon) = status.text diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml index c5b4439e273..db2040110fa 100644 --- a/app/views/ci/status/_dropdown_graph_badge.html.haml +++ b/app/views/ci/status/_dropdown_graph_badge.html.haml @@ -3,14 +3,15 @@ - subject = local_assigns.fetch(:subject) - status = subject.detailed_status(current_user) - klass = "ci-status-icon ci-status-icon-#{status.group}" -- tooltip = "#{subject.name} - #{status.label}" +- tooltip = "#{subject.name} - #{status.status_tooltip}" - if status.has_details? - = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do + = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, html: true, container: 'body' } do %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name + - else - .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } } + .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' } } %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index ecf186e3dc8..0b57ebedebd 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -1,5 +1,3 @@ -- builds = @build.pipeline.builds.to_a - %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } } .sidebar-container .blocks-container @@ -91,7 +89,8 @@ - HasStatus::ORDERED_STATUSES.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } - = link_to project_job_path(@project, build) do + - tooltip = build.tooltip_message + = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' }) do = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right') %span{ class: "ci-status-icon-#{build.status}" } = ci_icon_for_status(build.status) @@ -101,5 +100,4 @@ - else = build.id - if build.retried? - %span.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } - = sprite_icon('retry', size:16, css_class: 'icon-retry') + = sprite_icon('retry', size:16, css_class: 'icon-retry') diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index fa27ded7cc2..dece4dfe167 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -107,7 +107,7 @@ illustration_size: 'svg-430', title: _('This job has not started yet'), content: _('This job is in pending state and is waiting to be picked by a runner') - = render "sidebar" + = render "sidebar", builds: @builds .js-build-options{ data: javascript_build_options } diff --git a/changelogs/unreleased/44269-show-failure-reason-on-upgrade-tooltip-of-jobs.yml b/changelogs/unreleased/44269-show-failure-reason-on-upgrade-tooltip-of-jobs.yml new file mode 100644 index 00000000000..b3ae8ca7340 --- /dev/null +++ b/changelogs/unreleased/44269-show-failure-reason-on-upgrade-tooltip-of-jobs.yml @@ -0,0 +1,5 @@ +--- +title: Display error message on job's tooltip if this one fails +merge_request: 17782 +author: +type: added diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index c852d607373..20a319caf86 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -6,10 +6,12 @@ module Gitlab def self.extended_statuses [[Status::Build::Cancelable, Status::Build::Retryable], + [Status::Build::Failed], [Status::Build::FailedAllowed, Status::Build::Play, Status::Build::Stop], - [Status::Build::Action]] + [Status::Build::Action], + [Status::Build::Retried]] end def self.common_helpers diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb new file mode 100644 index 00000000000..155f4fc1343 --- /dev/null +++ b/lib/gitlab/ci/status/build/failed.rb @@ -0,0 +1,40 @@ +module Gitlab + module Ci + module Status + module Build + class Failed < Status::Extended + REASONS = { + 'unknown_failure' => 'unknown failure', + 'script_failure' => 'script failure', + 'api_failure' => 'API failure', + 'stuck_or_timeout_failure' => 'stuck or timeout failure', + 'runner_system_failure' => 'runner system failure', + 'missing_dependency_failure' => 'missing dependency failure' + }.freeze + + def status_tooltip + base_message + end + + def badge_tooltip + base_message + end + + def self.matches?(build, user) + build.failed? + end + + private + + def base_message + "#{s_('CiStatusLabel|failed')} #{description}" + end + + def description + "
(#{REASONS[subject.failure_reason]})" + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/build/failed_allowed.rb b/lib/gitlab/ci/status/build/failed_allowed.rb index dc90f398c7e..ca0046fb1f7 100644 --- a/lib/gitlab/ci/status/build/failed_allowed.rb +++ b/lib/gitlab/ci/status/build/failed_allowed.rb @@ -4,7 +4,7 @@ module Gitlab module Build class FailedAllowed < Status::Extended def label - 'failed (allowed to fail)' + "failed #{allowed_to_fail_title}" end def icon @@ -15,9 +15,19 @@ module Gitlab 'failed_with_warnings' end + def status_tooltip + "#{@status.status_tooltip} #{allowed_to_fail_title}" + end + def self.matches?(build, user) build.failed? && build.allow_failure? end + + private + + def allowed_to_fail_title + "(allowed to fail)" + end end end end diff --git a/lib/gitlab/ci/status/build/retried.rb b/lib/gitlab/ci/status/build/retried.rb new file mode 100644 index 00000000000..6e190e4ee3c --- /dev/null +++ b/lib/gitlab/ci/status/build/retried.rb @@ -0,0 +1,17 @@ +module Gitlab + module Ci + module Status + module Build + class Retried < Status::Extended + def status_tooltip + @status.status_tooltip + " (retried)" + end + + def self.matches?(build, user) + build.retried? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index d4fd83b93f8..daab6bb2de5 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -57,6 +57,16 @@ module Gitlab def action_title raise NotImplementedError end + + # Hint that appears on all the pipeline graph tooltips and builds on the right sidebar in Job detail view + def status_tooltip + label + end + + # Hint that appears on the build badges + def badge_tooltip + subject.status + end end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 30235a3a876..fdacbe6c3f1 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -238,5 +238,10 @@ FactoryBot.define do trait :protected do protected true end + + trait :script_failure do + failed + failure_reason 1 + end end end diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 4c49cff30d4..b7eee39052a 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -34,4 +34,26 @@ describe 'User browses a job', :js do expect(build.project.running_or_pending_build_count).to eq(build.project.builds.running_or_pending.count(:all)) end + + context 'with a failed job' do + let!(:build) { create(:ci_build, :failed, pipeline: pipeline) } + + it 'displays the failure reason' do + within('.builds-container') do + build_link = first('.build-job > a') + expect(build_link['data-title']).to eq('test - failed
(unknown failure)') + end + end + end + + context 'when a failed job has been retried' do + let!(:build) { create(:ci_build, :failed, :retried, pipeline: pipeline) } + + it 'displays the failure reason and retried label' do + within('.builds-container') do + build_link = first('.build-job > a') + expect(build_link['data-title']).to eq('test - failed
(unknown failure) (retried)') + end + end + end end diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb index 767777f3bf9..36ebbeadd4a 100644 --- a/spec/features/projects/jobs/user_browses_jobs_spec.rb +++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb @@ -29,4 +29,15 @@ describe 'User browses jobs' do expect(ci_lint_tool_link[:href]).to end_with(ci_lint_path) end end + + context 'with a failed job' do + let!(:build) { create(:ci_build, :coverage, :failed, pipeline: pipeline) } + + it 'displays a tooltip with the failure reason' do + page.within('.ci-table') do + failed_job_link = page.find('.ci-failed') + expect(failed_job_link[:title]).to eq('Failed
(unknown failure)') + end + end + end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 266ef693d0b..990e5c4d9df 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -115,6 +115,13 @@ describe 'Pipeline', :js do expect(page).not_to have_content('Retry job') end + + it 'should include the failure reason' do + page.within('#ci-badge-test') do + build_link = page.find('.js-pipeline-graph-job-link') + expect(build_link['data-original-title']).to eq('test - failed
(unknown failure)') + end + end end context 'when pipeline has manual jobs' do @@ -289,6 +296,15 @@ describe 'Pipeline', :js do it { expect(build_manual.reload).to be_pending } end + + context 'failed jobs' do + it 'displays a tooltip with the failure reason' do + page.within('.ci-table') do + failed_job_link = page.find('.ci-failed') + expect(failed_job_link[:title]).to eq('Failed
(unknown failure)') + end + end + end end describe 'GET /:project/pipelines/:id/failures' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 0e81c6c629a..6e63e0f0b49 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -394,6 +394,23 @@ describe 'Pipelines', :js do expect(build.reload).to be_canceled end end + + context 'for a failed pipeline' do + let!(:build) do + create(:ci_build, :failed, pipeline: pipeline, + stage: 'build', + name: 'build') + end + + it 'should display the failure reason' do + find('.js-builds-dropdown-button').click + + within('.js-builds-dropdown-list') do + build_element = page.find('.mini-pipeline-graph-dropdown-item') + expect(build_element['data-title']).to eq('build - failed
(unknown failure)') + end + end + end end context 'with pagination' do diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js index ce181a1e515..c9677ae209a 100644 --- a/spec/javascripts/pipelines/graph/job_component_spec.js +++ b/spec/javascripts/pipelines/graph/job_component_spec.js @@ -13,6 +13,7 @@ describe('pipeline graph job component', () => { icon: 'icon_status_success', text: 'passed', label: 'passed', + tooltip: 'passed', group: 'success', details_path: '/root/ci-mock/builds/4256', has_details: true, @@ -137,6 +138,7 @@ describe('pipeline graph job component', () => { status: { icon: 'icon_status_success', label: 'success', + tooltip: 'success', }, }, }); diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb index d612d29e3e0..bdec582b57b 100644 --- a/spec/lib/gitlab/ci/status/build/action_spec.rb +++ b/spec/lib/gitlab/ci/status/build/action_spec.rb @@ -53,4 +53,14 @@ describe Gitlab::Ci::Status::Build::Action do end end end + + describe '#badge_tooltip' do + let(:user) { create(:user) } + let(:build) { create(:ci_build, :non_playable) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + it 'returns the status' do + expect(subject.badge_tooltip).to eq('created') + end + end end diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb index 9cdebaa5cf2..3ef0b6817e9 100644 --- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -40,6 +40,24 @@ describe Gitlab::Ci::Status::Build::Cancelable do end end + describe '#status_tooltip' do + it 'does not override status status_tooltip' do + expect(status).to receive(:status_tooltip) + + subject.status_tooltip + end + end + + describe '#badge_tooltip' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + it 'returns the status' do + expect(subject.badge_tooltip).to eq('pending') + end + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index d196bc6a4c2..bbfa60169a1 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -48,11 +48,11 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Build::Retryable] + .to eq [Gitlab::Ci::Status::Build::Retryable, Gitlab::Ci::Status::Build::Failed] end - it 'fabricates a retryable build status' do - expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + it 'fabricates a failed build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Failed end it 'fabricates status with correct details' do @@ -60,6 +60,7 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.icon).to eq 'status_failed' expect(status.favicon).to eq 'favicon_status_failed' expect(status.label).to eq 'failed' + expect(status.status_tooltip).to eq 'failed
(unknown failure)' expect(status).to have_details expect(status).to have_action end @@ -75,6 +76,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'matches correct extended statuses' do expect(factory.extended_statuses) .to eq [Gitlab::Ci::Status::Build::Retryable, + Gitlab::Ci::Status::Build::Failed, Gitlab::Ci::Status::Build::FailedAllowed] end diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb index 99a5a7e4aca..bfaa508785e 100644 --- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb +++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::FailedAllowed do let(:status) { double('core status') } let(:user) { double('user') } + let(:build) { create(:ci_build, :failed, :allowed_to_fail) } subject do described_class.new(status) @@ -68,6 +69,28 @@ describe Gitlab::Ci::Status::Build::FailedAllowed do end end + describe '#badge_tooltip' do + let(:user) { create(:user) } + let(:failed_status) { Gitlab::Ci::Status::Failed.new(build, user) } + let(:build_status) { Gitlab::Ci::Status::Build::Failed.new(failed_status) } + let(:status) { described_class.new(build_status) } + + it 'does override badge_tooltip' do + expect(status.badge_tooltip).to eq('failed
(unknown failure)') + end + end + + describe '#status_tooltip' do + let(:user) { create(:user) } + let(:failed_status) { Gitlab::Ci::Status::Failed.new(build, user) } + let(:build_status) { Gitlab::Ci::Status::Build::Failed.new(failed_status) } + let(:status) { described_class.new(build_status) } + + it 'does override status_tooltip' do + expect(status.status_tooltip).to eq 'failed
(unknown failure) (allowed to fail)' + end + end + describe '.matches?' do subject { described_class.matches?(build, user) } diff --git a/spec/lib/gitlab/ci/status/build/failed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_spec.rb new file mode 100644 index 00000000000..cadb424ea2c --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/failed_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Failed do + let(:build) { create(:ci_build, :script_failure) } + let(:status) { double('core status') } + let(:user) { double('user') } + + subject { described_class.new(status) } + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'does not override status icon' do + expect(status).to receive(:icon) + + subject.icon + end + end + + describe '#group' do + it 'does not override status group' do + expect(status).to receive(:group) + + subject.group + end + end + + describe '#favicon' do + it 'does not override status label' do + expect(status).to receive(:favicon) + + subject.favicon + end + end + + describe '#label' do + it 'does not override label' do + expect(status).to receive(:label) + + subject.label + end + end + + describe '#badge_tooltip' do + let(:user) { create(:user) } + let(:status) { Gitlab::Ci::Status::Failed.new(build, user) } + + it 'does override badge_tooltip' do + expect(subject.badge_tooltip).to eq 'failed
(script failure)' + end + end + + describe '#status_tooltip' do + let(:user) { create(:user) } + let(:status) { Gitlab::Ci::Status::Failed.new(build, user) } + + it 'does override status_tooltip' do + expect(subject.status_tooltip).to eq 'failed
(script failure)' + end + end + + describe '.matches?' do + context 'with a failed build' do + it 'returns true' do + expect(described_class.matches?(build, user)).to be_truthy + end + end + + context 'with any other type of build' do + let(:build) { create(:ci_build, :success) } + + it 'returns false' do + expect(described_class.matches?(build, user)).to be_falsy + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 81d5f553fd1..35e47cd2526 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -14,6 +14,22 @@ describe Gitlab::Ci::Status::Build::Play do end end + describe '#status_tooltip' do + it 'does not override status status_tooltip' do + expect(status).to receive(:status_tooltip) + + subject.status_tooltip + end + end + + describe '#badge_tooltip' do + it 'does not override status badge_tooltip' do + expect(status).to receive(:badge_tooltip) + + subject.badge_tooltip + end + end + describe '#has_action?' do context 'when user is allowed to update build' do context 'when user is allowed to trigger protected action' do diff --git a/spec/lib/gitlab/ci/status/build/retried_spec.rb b/spec/lib/gitlab/ci/status/build/retried_spec.rb new file mode 100644 index 00000000000..ee9acaf1c21 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/retried_spec.rb @@ -0,0 +1,96 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Retried do + let(:build) { create(:ci_build, :retried) } + let(:status) { double('core status') } + let(:user) { double('user') } + + subject { described_class.new(status) } + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'does not override status icon' do + expect(status).to receive(:icon) + + subject.icon + end + end + + describe '#group' do + it 'does not override status group' do + expect(status).to receive(:group) + + subject.group + end + end + + describe '#favicon' do + it 'does not override status label' do + expect(status).to receive(:favicon) + + subject.favicon + end + end + + describe '#label' do + it 'does not override status label' do + expect(status).to receive(:label) + + subject.label + end + end + + describe '#badge_tooltip' do + let(:user) { create(:user) } + let(:build) { create(:ci_build, :retried) } + let(:status) { Gitlab::Ci::Status::Success.new(build, user) } + + it 'returns status' do + expect(status.badge_tooltip).to eq('pending') + end + end + + describe '#status_tooltip' do + let(:user) { create(:user) } + + context 'with a failed build' do + let(:build) { create(:ci_build, :failed, :retried) } + let(:failed_status) { Gitlab::Ci::Status::Failed.new(build, user) } + let(:status) { Gitlab::Ci::Status::Build::Failed.new(failed_status) } + + it 'does override status_tooltip' do + expect(subject.status_tooltip).to eq 'failed
(unknown failure) (retried)' + end + end + + context 'with another build' do + let(:build) { create(:ci_build, :retried) } + let(:status) { Gitlab::Ci::Status::Success.new(build, user) } + + it 'does override status_tooltip' do + expect(subject.status_tooltip).to eq 'passed (retried)' + end + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'with a retried build' do + it { is_expected.to be_truthy } + end + + context 'with a build that has not been retried' do + let(:build) { create(:ci_build, :success) } + + it { is_expected.to be_falsy } + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb index 14d42e0d70f..0c5099b7da5 100644 --- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -40,6 +40,24 @@ describe Gitlab::Ci::Status::Build::Retryable do end end + describe '#status_tooltip' do + it 'does not override status status_tooltip' do + expect(status).to receive(:status_tooltip) + + subject.status_tooltip + end + end + + describe '#badge_tooltip' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + it 'does return status' do + expect(status.badge_tooltip).to eq('pending') + end + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index 18e250772f0..f16fc5c9205 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -77,4 +77,24 @@ describe Gitlab::Ci::Status::Build::Stop do end end end + + describe '#status_tooltip' do + it 'does not override status status_tooltip' do + expect(status).to receive(:status_tooltip) + + subject.status_tooltip + end + end + + describe '#badge_tooltip' do + let(:user) { create(:user) } + let(:build) { create(:ci_build, :playable) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + it 'does not override status badge_tooltip' do + expect(status).to receive(:badge_tooltip) + + subject.badge_tooltip + end + end end diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb index 4582354e739..6d05545d1d8 100644 --- a/spec/lib/gitlab/ci/status/success_warning_spec.rb +++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb @@ -1,8 +1,10 @@ require 'spec_helper' describe Gitlab::Ci::Status::SuccessWarning do + let(:status) { double('status') } + subject do - described_class.new(double('status')) + described_class.new(status) end describe '#test' do diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 1a8001be6ab..cc16d0f156b 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -72,13 +72,44 @@ describe Ci::BuildPresenter do end end - context 'when build is not auto-canceled' do - before do - expect(build).to receive(:auto_canceled?).and_return(false) - end + context 'when build failed' do + let(:build) { create(:ci_build, :failed, pipeline: pipeline) } - it 'does not have a status title' do - expect(presenter.status_title).to be_nil + it 'returns the reason of failure' do + status_title = presenter.status_title + + expect(status_title).to eq('Failed
(unknown failure)') + end + end + + context 'when build has failed && retried' do + let(:build) { create(:ci_build, :failed, :retried, pipeline: pipeline) } + + it 'does not include retried title' do + status_title = presenter.status_title + + expect(status_title).not_to include('(retried)') + expect(status_title).to eq('Failed
(unknown failure)') + end + end + + context 'when build has failed and is allowed to' do + let(:build) { create(:ci_build, :failed, :allowed_to_fail, pipeline: pipeline) } + + it 'returns the reason of failure' do + status_title = presenter.status_title + + expect(status_title).to eq('Failed
(unknown failure)') + end + end + + context 'For any other build' do + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + + it 'returns the status' do + tooltip_description = presenter.status_title + + expect(tooltip_description).to eq('Success') end end end @@ -134,4 +165,56 @@ describe Ci::BuildPresenter do end end end + + describe '#tooltip_message' do + context 'When build has failed' do + let(:build) { create(:ci_build, :script_failure, pipeline: pipeline) } + + it 'returns the reason of failure' do + tooltip = subject.tooltip_message + + expect(tooltip).to eq("#{build.name} - failed
(script failure)") + end + end + + context 'When build has failed and retried' do + let(:build) { create(:ci_build, :script_failure, :retried, pipeline: pipeline) } + + it 'should include the reason of failure and the retried title' do + tooltip = subject.tooltip_message + + expect(tooltip).to eq("#{build.name} - failed
(script failure) (retried)") + end + end + + context 'When build has failed and is allowed to' do + let(:build) { create(:ci_build, :script_failure, :allowed_to_fail, pipeline: pipeline) } + + it 'should include the reason of failure' do + tooltip = subject.tooltip_message + + expect(tooltip).to eq("#{build.name} - failed
(script failure) (allowed to fail)") + end + end + + context 'For any other build (no retried)' do + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + + it 'should include build name and status' do + tooltip = subject.tooltip_message + + expect(tooltip).to eq("#{build.name} - passed") + end + end + + context 'For any other build (retried)' do + let(:build) { create(:ci_build, :success, :retried, pipeline: pipeline) } + + it 'should include build name and status' do + tooltip = subject.tooltip_message + + expect(tooltip).to eq("#{build.name} - passed (retried)") + end + end + end end diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb index 9673b11c2a2..98cd15e248b 100644 --- a/spec/serializers/build_serializer_spec.rb +++ b/spec/serializers/build_serializer_spec.rb @@ -28,15 +28,31 @@ describe BuildSerializer do end describe '#represent_status' do - context 'when represents only status' do - let(:resource) { create(:ci_build) } + context 'for a failed build' do + let(:resource) { create(:ci_build, :failed) } let(:status) { resource.detailed_status(double('user')) } subject { serializer.represent_status(resource) } it 'serializes only status' do expect(subject[:text]).to eq(status.text) - expect(subject[:label]).to eq(status.label) + expect(subject[:label]).to eq('failed') + expect(subject[:tooltip]).to eq('failed
(unknown failure)') + expect(subject[:icon]).to eq(status.icon) + expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") + end + end + + context 'for any other type of build' do + let(:resource) { create(:ci_build, :success) } + let(:status) { resource.detailed_status(double('user')) } + + subject { serializer.represent_status(resource) } + + it 'serializes only status' do + expect(subject[:text]).to eq(status.text) + expect(subject[:label]).to eq('passed') + expect(subject[:tooltip]).to eq('passed') expect(subject[:icon]).to eq(status.icon) expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") end diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb index 026360e91a3..24a6f1a2a8a 100644 --- a/spec/serializers/job_entity_spec.rb +++ b/spec/serializers/job_entity_spec.rb @@ -38,7 +38,7 @@ describe JobEntity do it 'contains details' do expect(subject).to include :status - expect(subject[:status]).to include :icon, :favicon, :text, :label + expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip end context 'when job is retryable' do @@ -126,7 +126,29 @@ describe JobEntity do it 'contains details' do expect(subject).to include :status - expect(subject[:status]).to include :icon, :favicon, :text, :label + expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip + end + end + + context 'when job failed' do + let(:job) { create(:ci_build, :script_failure) } + + describe 'status' do + it 'should contain the failure reason inside label' do + expect(subject[:status]).to include :icon, :favicon, :text, :label, :tooltip + expect(subject[:status][:label]).to eq('failed') + expect(subject[:status][:tooltip]).to eq('failed
(script failure)') + end + end + end + + context 'when job passed' do + let(:job) { create(:ci_build, :success) } + + describe 'status' do + it 'should not contain the failure reason inside label' do + expect(subject[:status][:label]).to eq('passed') + end end end end diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 248552d1858..2473c561f4b 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -30,7 +30,7 @@ describe PipelineEntity do expect(subject).to include :details expect(subject[:details]) .to include :duration, :finished_at - expect(subject[:details][:status]).to include :icon, :favicon, :text, :label + expect(subject[:details][:status]).to include :icon, :favicon, :text, :label, :tooltip end it 'contains flags' do diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb index 40e303f7b89..2034c7891ef 100644 --- a/spec/serializers/stage_entity_spec.rb +++ b/spec/serializers/stage_entity_spec.rb @@ -26,7 +26,7 @@ describe StageEntity do end it 'contains detailed status' do - expect(subject[:status]).to include :text, :label, :group, :icon + expect(subject[:status]).to include :text, :label, :group, :icon, :tooltip expect(subject[:status][:label]).to eq 'passed' end diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb index 70402bac2e2..559475e571c 100644 --- a/spec/serializers/status_entity_spec.rb +++ b/spec/serializers/status_entity_spec.rb @@ -16,7 +16,7 @@ describe StatusEntity do subject { entity.as_json } it 'contains status details' do - expect(subject).to include :text, :icon, :favicon, :label, :group + expect(subject).to include :text, :icon, :favicon, :label, :group, :tooltip expect(subject).to include :has_details, :details_path expect(subject[:favicon]).to match_asset_path('/assets/ci_favicons/favicon_status_success.ico') end diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb index 6a67da79ec5..9e692159bd0 100644 --- a/spec/views/projects/jobs/show.html.haml_spec.rb +++ b/spec/views/projects/jobs/show.html.haml_spec.rb @@ -1,8 +1,10 @@ require 'spec_helper' describe 'projects/jobs/show' do + let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, pipeline: pipeline) } + let(:builds) { project.builds.present(current_user: user) } let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit.id) @@ -11,6 +13,7 @@ describe 'projects/jobs/show' do before do assign(:build, build.present) assign(:project, project) + assign(:builds, builds) allow(view).to receive(:can?).and_return(true) end From 8fd61a7e44614a2e4ab24a52d43fe2b676d6532e Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Thu, 5 Apr 2018 17:14:08 -0400 Subject: [PATCH 053/146] Add changelog item --- ...45070-prometheus-integration-via-kubernetes-is-broken.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/45070-prometheus-integration-via-kubernetes-is-broken.yml diff --git a/changelogs/unreleased/45070-prometheus-integration-via-kubernetes-is-broken.yml b/changelogs/unreleased/45070-prometheus-integration-via-kubernetes-is-broken.yml new file mode 100644 index 00000000000..046785deb3f --- /dev/null +++ b/changelogs/unreleased/45070-prometheus-integration-via-kubernetes-is-broken.yml @@ -0,0 +1,5 @@ +--- +title: Work around Prometheus Helm chart name changes to fix integration +merge_request: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18206/ +author: joshlambert +type: fixed From 78ff68a2108bb78a9fa488ff995b2111ad837c72 Mon Sep 17 00:00:00 2001 From: Lukas Eipert Date: Fri, 6 Apr 2018 08:22:57 +0000 Subject: [PATCH 054/146] Polish design of domain verification for Gitlab Pages --- app/assets/stylesheets/framework/lists.scss | 13 ++-- .../stylesheets/framework/typography.scss | 5 ++ app/assets/stylesheets/pages/pages.scss | 60 +++++++++++++++++++ app/views/projects/pages/_list.html.haml | 37 ++++++------ app/views/projects/pages/show.html.haml | 3 +- .../projects/pages_domains/edit.html.haml | 2 +- .../projects/pages_domains/new.html.haml | 2 +- .../projects/pages_domains/show.html.haml | 56 ++++++++++------- ...15-update-design-for-verifying-domains.yml | 5 ++ 9 files changed, 135 insertions(+), 48 deletions(-) create mode 100644 app/assets/stylesheets/pages/pages.scss create mode 100644 changelogs/unreleased/43215-update-design-for-verifying-domains.yml diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 7e829826eba..f1a8a46dda4 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -24,6 +24,10 @@ color: $list-text-disabled-color; } + &:not(.ui-sort-disabled):hover { + background: $row-hover; + } + &.unstyled { &:hover { background: none; @@ -34,14 +38,15 @@ background-color: $list-warning-row-bg; border-color: $list-warning-row-border; color: $list-warning-row-color; + + &:hover { + background: $list-warning-row-bg; + } + } &.smoke { background-color: $gray-light; } - &:not(.ui-sort-disabled):hover { - background: $row-hover; - } - &:last-child { border-bottom: 0; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 294c59f037f..9e1371648ed 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -289,6 +289,11 @@ body { &:last-child { margin-bottom: 0; } + + &.with-button { + line-height: 34px; + } + } .page-title-empty { diff --git a/app/assets/stylesheets/pages/pages.scss b/app/assets/stylesheets/pages/pages.scss new file mode 100644 index 00000000000..fb42dee66d2 --- /dev/null +++ b/app/assets/stylesheets/pages/pages.scss @@ -0,0 +1,60 @@ +.pages-domain-list { + &-item { + position: relative; + display: flex; + align-items: center; + + .domain-status { + display: inline-flex; + left: $gl-padding; + position: absolute; + } + + .domain-name { + flex-grow: 1; + } + + } + + &.has-verification-status > li { + padding-left: 3 * $gl-padding; + } + +} + +.status-badge { + + display: inline-flex; + margin-bottom: $gl-padding-8; + + // Most of the following settings "stolen" from btn-sm + // Border radius is overwritten for both + .label, + .btn { + padding: $gl-padding-4 $gl-padding-8; + font-size: $gl-font-size; + line-height: $gl-btn-line-height; + border-radius: 0; + display: flex; + align-items: center; + } + + .btn svg { + top: auto; + } + + :first-child { + border-bottom-left-radius: $border-radius-default; + border-top-left-radius: $border-radius-default; + } + + :not(:first-child) { + border-left: 0; + } + + :last-child { + border-bottom-right-radius: $border-radius-default; + border-top-right-radius: $border-radius-default; + } + +} diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index 75df92b05a7..27bbe52a714 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -1,28 +1,29 @@ +- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? + - if can?(current_user, :update_pages, @project) && @domains.any? .panel.panel-default .panel-heading Domains (#{@domains.count}) - %ul.well-list - - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? + %ul.well-list.pages-domain-list{ class: ("has-verification-status" if verification_enabled) } - @domains.each do |domain| - %li - .pull-right + %li.pages-domain-list-item.unstyled + - if verification_enabled + - tooltip, status = domain.unverified? ? [_('Unverified'), 'failed'] : [_('Verified'), 'success'] + .domain-status.ci-status-icon.has-tooltip{ class: "ci-status-icon-#{status}", title: tooltip } + = sprite_icon("status_#{status}", size: 16 ) + .domain-name + = link_to domain.url do + = domain.url + = icon('external-link') + - if domain.subject + %p + %span.label.label-gray Certificate: #{domain.subject} + - if domain.expired? + %span.label.label-danger Expired + %div = link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped" = link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" - .clearfix - - if verification_enabled - - tooltip, status = domain.unverified? ? ['Unverified', 'failed'] : ['Verified', 'success'] - = link_to domain.url, title: tooltip, class: 'has-tooltip' do - = sprite_icon("status_#{status}", size: 16, css_class: "has-tooltip ci-status-icon ci-status-icon-#{status}") - = domain.domain - - else - = link_to domain.domain, domain.url - %p - - if domain.subject - %span.label.label-gray Certificate: #{domain.subject} - - if domain.expired? - %span.label.label-danger Expired - if verification_enabled && domain.unverified? %li.warning-row #{domain.domain} is not verified. To learn how to verify ownership, visit your - = link_to 'domain details', project_pages_domain_path(@project, domain) + #{link_to 'domain details', project_pages_domain_path(@project, domain)}. diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index f17d9d24db6..6adaea799b2 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -1,11 +1,10 @@ - page_title 'Pages' -%h3.page_title +%h3.page-title.with-button Pages - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https) = link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do - %i.fa.fa-plus New Domain %p.light diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml index 5645a4604bf..6c404990492 100644 --- a/app/views/projects/pages_domains/edit.html.haml +++ b/app/views/projects/pages_domains/edit.html.haml @@ -1,7 +1,7 @@ - add_to_breadcrumbs "Pages", project_pages_path(@project) - breadcrumb_title @domain.domain - page_title @domain.domain -%h3.page_title +%h3.page-title = @domain.domain %hr.clearfix %div diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml index e49163880c7..269df803a2b 100644 --- a/app/views/projects/pages_domains/new.html.haml +++ b/app/views/projects/pages_domains/new.html.haml @@ -1,6 +1,6 @@ - add_to_breadcrumbs "Pages", project_pages_path(@project) - page_title 'New Pages Domain' -%h3.page_title +%h3.page-title New Pages Domain %hr.clearfix %div diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml index ba0713daee9..44d66f3b2d0 100644 --- a/app/views/projects/pages_domains/show.html.haml +++ b/app/views/projects/pages_domains/show.html.haml @@ -1,17 +1,19 @@ - add_to_breadcrumbs "Pages", project_pages_path(@project) - breadcrumb_title @domain.domain - page_title "#{@domain.domain}", 'Pages Domains' +- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}." - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? -- if verification_enabled && @domain.unverified? - %p.alert.alert-warning - %strong - This domain is not verified. You will need to verify ownership before - access is enabled. -%h3.page-title - Pages Domain +- if verification_enabled && @domain.unverified? + = content_for :flash_message do + .alert.alert-warning + .container-fluid.container-limited + This domain is not verified. You will need to verify ownership before access is enabled. + +%h3.page-title.with-button = link_to 'Edit', edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success pull-right' + Pages Domain .table-holder %table.table @@ -19,31 +21,41 @@ %td Domain %td - = link_to @domain.domain, @domain.url + = link_to @domain.url do + = @domain.url + = icon('external-link') %tr %td DNS %td - %p - To access this domain create a new DNS record: - %pre - #{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}. + .input-group + = text_field_tag :domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true + .input-group-btn + = clipboard_button(target: '#domain_dns', class: 'btn-default hidden-xs') + %p.help-block + To access this domain create a new DNS record + - if verification_enabled + - verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}" %tr %td Verification status %td - %p + = form_tag verify_project_pages_domain_path(@project, @domain) do + .status-badge + - text, status = @domain.unverified? ? [_('Unverified'), 'label-danger'] : [_('Verified'), 'label-success'] + .label{ class: status } + = text + %button.btn.has-tooltip{ type: "submit", data: { container: 'body' }, title: _("Retry verification") } + = sprite_icon('redo') + .input-group + = text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true + .input-group-btn + = clipboard_button(target: '#domain_verification', class: 'btn-default hidden-xs') + %p.help-block - help_link = help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record') - To #{link_to 'verify ownership', help_link} of your domain, create - this DNS record: - %pre - #{@domain.verification_domain} TXT #{@domain.keyed_verification_code} - %p - - if @domain.verified? - #{@domain.domain} has been successfully verified. - - else - = button_to 'Verify ownership', verify_project_pages_domain_path(@project, @domain), class: 'btn btn-save btn-sm' + To #{link_to 'verify ownership', help_link} of your domain, + add the above key to a TXT record within to your DNS configuration. %tr %td diff --git a/changelogs/unreleased/43215-update-design-for-verifying-domains.yml b/changelogs/unreleased/43215-update-design-for-verifying-domains.yml new file mode 100644 index 00000000000..8326540f7b2 --- /dev/null +++ b/changelogs/unreleased/43215-update-design-for-verifying-domains.yml @@ -0,0 +1,5 @@ +--- +title: Polish design for verifying domains +merge_request: 17767 +author: +type: changed From 847f1667c89831213859d62ca66fbd55181fb129 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 6 Apr 2018 10:41:58 +0200 Subject: [PATCH 055/146] Document unsupported variables for dynamic environments --- doc/ci/environments.md | 15 ++++++++++++--- doc/ci/variables/README.md | 4 ++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 58c4a71cef9..b3d9f0bc96c 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -247,10 +247,19 @@ declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is the basis of [Review apps](review_apps/index.md). >**Note:** -The `name` and `url` parameters can use any of the defined CI variables, +The `name` and `url` parameters can use most of the defined CI variables, including predefined, secure variables and `.gitlab-ci.yml` -[`variables`](yaml/README.md#variables). -You however cannot use variables defined under `script` or on the Runner's side. +[`variables`](yaml/README.md#variables). You however cannot use variables +defined under `script` or on the Runner's side. There are other variables that +are unsupported in environment name context: +- `CI_JOB_ID` +- `CI_JOB_TOKEN` +- `CI_BUILD_ID` +- `CI_BUILD_TOKEN` +- `CI_REGISTRY_USER` +- `CI_REGISTRY_PASSWORD` +- `CI_REPOSITORY_URL` +- `CI_ENVIRONMENT_URL` GitLab Runner exposes various [environment variables][variables] when a job runs, and as such, you can use them as environment names. Let's add another job in diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 381405a9db9..4a504a98902 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -547,6 +547,9 @@ You can find a full list of unsupported variables below: - `CI_REPOSITORY_URL` - `CI_ENVIRONMENT_URL` +These variables are also not supported in a contex of a +[dynamic environment name][dynamic-environments]. + [ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI secret variables" [eep]: https://about.gitlab.com/products/ "Available only in GitLab Premium" [envs]: ../environments.md @@ -558,3 +561,4 @@ You can find a full list of unsupported variables below: [triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger [subgroups]: ../../user/group/subgroups/index.md [builds-policies]: ../yaml/README.md#only-and-except-complex +[dynamic-environments]: ../environments.md#dynamic-environments From 388f43b6111bf35561988d327c462ab75afc34f6 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 13:55:55 +0530 Subject: [PATCH 056/146] Add map for supported noteable types --- app/assets/javascripts/notes/constants.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 68f8cb1cf1e..c4de4826eda 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -14,3 +14,9 @@ export const EPIC_NOTEABLE_TYPE = 'epic'; export const MERGE_REQUEST_NOTEABLE_TYPE = 'merge_request'; export const UNRESOLVE_NOTE_METHOD_NAME = 'delete'; export const RESOLVE_NOTE_METHOD_NAME = 'post'; + +export const NOTEABLE_TYPE_MAPPING = { + Issue: ISSUE_NOTEABLE_TYPE, + MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE, + Epic: EPIC_NOTEABLE_TYPE, +}; From 15195f67840cd5369d776938b62b02aa939a4270 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 13:56:15 +0530 Subject: [PATCH 057/146] Use noteable type map from constants --- app/assets/javascripts/notes/mixins/noteable.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/assets/javascripts/notes/mixins/noteable.js b/app/assets/javascripts/notes/mixins/noteable.js index 5bf8216a1f3..b68543d71c8 100644 --- a/app/assets/javascripts/notes/mixins/noteable.js +++ b/app/assets/javascripts/notes/mixins/noteable.js @@ -9,16 +9,7 @@ export default { }, computed: { noteableType() { - switch (this.note.noteable_type) { - case 'MergeRequest': - return constants.MERGE_REQUEST_NOTEABLE_TYPE; - case 'Issue': - return constants.ISSUE_NOTEABLE_TYPE; - case 'Epic': - return constants.EPIC_NOTEABLE_TYPE; - default: - return ''; - } + return constants.NOTEABLE_TYPE_MAPPING[this.note.noteable_type]; }, }, }; From 223eeae1a774df4f8d7f132040ba10547184ec68 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 13:56:54 +0530 Subject: [PATCH 058/146] Return noteableType from app initial config --- app/assets/javascripts/notes/components/notes_app.vue | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 5bd81c7cad6..ebfc827ac57 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -49,16 +49,7 @@ export default { computed: { ...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']), noteableType() { - // FIXME -- @fatihacet Get this from JSON data. - const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants; - - if (this.noteableData.noteableType === EPIC_NOTEABLE_TYPE) { - return EPIC_NOTEABLE_TYPE; - } - - return this.noteableData.merge_params - ? MERGE_REQUEST_NOTEABLE_TYPE - : ISSUE_NOTEABLE_TYPE; + return this.noteableData.noteableType; }, allNotes() { if (this.isLoading) { From a983583ae8f3dd3e8cafb13d3934636cb095cfcc Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 13:57:26 +0530 Subject: [PATCH 059/146] Set `noteable_type` to use in notesApp --- app/views/projects/issues/_discussion.html.haml | 1 + app/views/projects/merge_requests/show.html.haml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index cdfc3e232c5..816f2fa816d 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -8,4 +8,5 @@ %section.js-vue-notes-event #js-vue-notes{ data: { notes_data: notes_data(@issue), noteable_data: serialize_issuable(@issue), + noteable_type: 'issue', current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } } diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 9866cc716ee..15a0e4d7ef5 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -80,6 +80,7 @@ - if has_vue_discussions_cookie? #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request), noteable_data: serialize_issuable(@merge_request), + noteable_type: 'merge_request', current_user_data: UserSerializer.new.represent(current_user).to_json} } #commits.commits.tab-pane From 7544a555aafc4bd021e5ae73bef6a650e6144b38 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 5 Apr 2018 14:57:22 +0530 Subject: [PATCH 060/146] Add noteableType to mock data object --- spec/javascripts/notes/mock_data.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 2d88cee61f1..24388fba219 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -52,6 +52,7 @@ export const noteableDataMock = { updated_at: '2017-08-04T09:53:01.226Z', updated_by_id: 1, web_url: '/gitlab-org/gitlab-ce/issues/26', + noteableType: 'issue', }; export const lastFetchedAt = '1501862675'; From bf54baa73bb648be3e13a74c14641e3916b54269 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 6 Apr 2018 09:17:35 +0000 Subject: [PATCH 061/146] Add note about contributing back to GitLab to plugins.md --- doc/administration/plugins.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/administration/plugins.md b/doc/administration/plugins.md index c91ac3012b9..dedb1f913d7 100644 --- a/doc/administration/plugins.md +++ b/doc/administration/plugins.md @@ -1,5 +1,9 @@ # Plugins +**Note:** Instead of writing and supporting your own plugin you can make changes +directly to the GitLab source code and contribute it back to the upstream. This way we can +ensure functionality is preserved across versions and covered by tests. + **Note:** Plugins must be configured on the filesystem of the GitLab server. Only GitLab server administrators will be able to complete these tasks. Please explore [system hooks] or [webhooks] as an option if you do not From 43ef375e084884ed4260ac9c9de8f601d5594c90 Mon Sep 17 00:00:00 2001 From: Shah El-Rahman Date: Fri, 6 Apr 2018 09:36:22 +0000 Subject: [PATCH 062/146] Add confirmation modal to "Change username" --- .../account/components/update_username.vue | 121 ++++++++++++ .../javascripts/profile/account/index.js | 15 ++ .../vue_shared/components/gl_modal.vue | 79 ++++---- app/controllers/profiles_controller.rb | 18 +- app/views/profiles/accounts/show.html.haml | 16 +- ...rname-url-still-redirects-to-old-route.yml | 5 + spec/controllers/profiles_controller_spec.rb | 22 +++ spec/features/profile_spec.rb | 8 +- spec/features/profiles/account_spec.rb | 12 +- .../components/update_username_spec.js | 172 ++++++++++++++++++ 10 files changed, 401 insertions(+), 67 deletions(-) create mode 100644 app/assets/javascripts/profile/account/components/update_username.vue create mode 100644 changelogs/unreleased/41758-after-changing-username-url-still-redirects-to-old-route.yml create mode 100644 spec/javascripts/profile/account/components/update_username_spec.js diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue new file mode 100644 index 00000000000..e5de3f69b01 --- /dev/null +++ b/app/assets/javascripts/profile/account/components/update_username.vue @@ -0,0 +1,121 @@ + + diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js index 84049a1f0b7..59c13e1a042 100644 --- a/app/assets/javascripts/profile/account/index.js +++ b/app/assets/javascripts/profile/account/index.js @@ -1,10 +1,25 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; +import UpdateUsername from './components/update_username.vue'; import deleteAccountModal from './components/delete_account_modal.vue'; export default () => { Vue.use(Translate); + const updateUsernameElement = document.getElementById('update-username'); + // eslint-disable-next-line no-new + new Vue({ + el: updateUsernameElement, + components: { + UpdateUsername, + }, + render(createElement) { + return createElement('update-username', { + props: { ...updateUsernameElement.dataset }, + }); + }, + }); + const deleteAccountButton = document.getElementById('delete-account-button'); const deleteAccountModalEl = document.getElementById('delete-account-modal'); // eslint-disable-next-line no-new diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue index 67c9181c7b1..f28e5e2715d 100644 --- a/app/assets/javascripts/vue_shared/components/gl_modal.vue +++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue @@ -1,47 +1,42 @@